diff --git a/README.md b/README.md index a99cb2a51..95cacbc9f 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ mvn install # Run and use -We are not providing an off the shelf production ready hawkBit server. However, we recommend to check out the [Example Application](examples/hawkbit-example-app) for a runtime ready Spring Boot based server that is empowered by hawkBit. +We are not providing an off the shelf production ready hawkBit server. However, we recommend to check out the [Example Application](examples/hawkbit-example-app) for a runtime ready Spring Boot based update server that is empowered by hawkBit. # Releases and Roadmap @@ -20,8 +20,8 @@ We are not providing an off the shelf production ready hawkBit server. However, * The master branch contains future development towards 0.2. We are currently focusing on: * Rollout Management for large scale rollouts. * Clustering capabilities for the update server. - * Upgrade of Spring Boot and Vaadin. - * And of course tones of usability improvements and bug fixes. + * Upgrade of Spring Boot and Vaadin depedencies. + * And of course tons of usability improvements and bug fixes. ## Try out examples @@ -42,7 +42,7 @@ We are not providing an off the shelf production ready hawkBit server. However, `hawkbit-http-security` : implementation for security filters for HTTP. `hawkbit-rest-api` : API classes for the REST Management API. `hawkbit-rest-resource` : HTTP REST endpoints for the Management and the Direct Device API. -`hawkbit-rest-resource` : Vaadin UI. +`hawkbit-ui` : Vaadin UI. `hawkbit-cache-redis` : spring cache manager configuration and implementation with redis, distributed cache and distributed events. diff --git a/examples/hawkbit-device-simulator/README.md b/examples/hawkbit-device-simulator/README.md index b5db3d7ef..610a1d256 100644 --- a/examples/hawkbit-device-simulator/README.md +++ b/examples/hawkbit-device-simulator/README.md @@ -20,6 +20,25 @@ The simulator has user authentication enabled by default. Default credentials: This can be configured/disabled by spring boot properties ## Usage + +### Graphical User Interface +The device simulator comes with a graphical user interface which makes it very easy to generate dummy devices handled by the device simulator. +The status and the update progress of the simulated device are shown in the UI. +The UI can be accessed via the URL: +``` +http://localhost:8083 +``` + +`Basic Authentication Credentials are admin / admin` + + ![](src/main/images/generateScreenshot.png) + + ![](src/main/images/updateProcessScreenshot.png) + + ![](src/main/images/updateResultOverviewScreenshot.png) + + +### REST API The device simulator exposes an REST-API which can be used to trigger device creation. Optional parameters: diff --git a/examples/hawkbit-device-simulator/pom.xml b/examples/hawkbit-device-simulator/pom.xml index 9e9fc86dc..eaec9b91e 100644 --- a/examples/hawkbit-device-simulator/pom.xml +++ b/examples/hawkbit-device-simulator/pom.xml @@ -80,10 +80,52 @@ org.springframework.boot spring-boot-starter-log4j2 + + com.vaadin + vaadin-spring-boot-starter + 1.0.0 + + + com.vaadin + vaadin-push + org.springframework.boot spring-boot-autoconfigure + + org.springframework.boot + spring-boot-autoconfigure + + + com.google.guava + guava + 19.0 + + + com.netflix.feign + feign-jackson + 8.14.1 + + + com.netflix.feign + feign-core + 8.12.1 + + + com.jayway.jsonpath + json-path + - + + + + com.vaadin + vaadin-bom + 7.5.5 + pom + import + + + diff --git a/examples/hawkbit-device-simulator/src/main/images/generateScreenshot.png b/examples/hawkbit-device-simulator/src/main/images/generateScreenshot.png new file mode 100644 index 000000000..6c31c2d7d Binary files /dev/null and b/examples/hawkbit-device-simulator/src/main/images/generateScreenshot.png differ diff --git a/examples/hawkbit-device-simulator/src/main/images/updateProcessScreenshot.png b/examples/hawkbit-device-simulator/src/main/images/updateProcessScreenshot.png new file mode 100644 index 000000000..dd3652790 Binary files /dev/null and b/examples/hawkbit-device-simulator/src/main/images/updateProcessScreenshot.png differ diff --git a/examples/hawkbit-device-simulator/src/main/images/updateResultOverviewScreenshot.png b/examples/hawkbit-device-simulator/src/main/images/updateResultOverviewScreenshot.png new file mode 100644 index 000000000..78e16758f Binary files /dev/null and b/examples/hawkbit-device-simulator/src/main/images/updateResultOverviewScreenshot.png differ diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/AbstractSimulatedDevice.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/AbstractSimulatedDevice.java new file mode 100644 index 000000000..474acb6c4 --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/AbstractSimulatedDevice.java @@ -0,0 +1,180 @@ +/** + * 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.simulator; + +/** + * The bean of a simulated device which can be stored in the + * {@link DeviceSimulatorRepository} or shown in the UI. + * + * @author Michael Hirsch + * + */ +public abstract class AbstractSimulatedDevice { + + private String id; + private String tenant; + private Status status; + private double progress; + private String swversion = "unknown"; + private ResponseStatus responseStatus = ResponseStatus.SUCCESSFUL; + private Protocol protocol = Protocol.DMF_AMQP; + + private int nextPollCounterSec; + + /** + * Enum definition of the protocol to be used for the simulated device. + * + * @author Michael Hirsch + * + */ + public enum Protocol { + /** + * Device Management Federation API via AMQP, push mechanism. + */ + DMF_AMQP, + /** + * Direct Device Interface via HTTP, poll mechanism. + */ + DDI_HTTP; + } + + /** + * The current status of the simulated device. + * + * @author Michael Hirsch + * + */ + public enum Status { + /** + * device is in status unknown. + */ + UNKNWON, + /** + * device is in status pending which represents is updating software. + */ + PEDNING, + /** + * device has been updated successfully. + */ + FINISH, + /** + * device has been updated with an error. + */ + ERROR; + } + + /** + * The status to response to the hawkbit update server if an simulated + * update process should be respond with successful or failure update. + * + * @author Michael Hirsch + * + */ + public enum ResponseStatus { + /** + * updated has been successful and response the successful update. + */ + SUCCESSFUL, + /** + * updated has been not successful and response the error update. + */ + ERROR; + } + + /** + * empty constructor. + */ + AbstractSimulatedDevice() { + + } + + /** + * Creates a new simulated device. + * + * @param id + * the ID of the simulated device + * @param tenant + * the tenant of the simulated device + */ + AbstractSimulatedDevice(final String id, final String tenant, final Protocol protocol) { + this.id = id; + this.tenant = tenant; + this.status = Status.UNKNWON; + this.progress = 0.0; + this.protocol = protocol; + } + + /** + * Method to clean-up resource e.g. when the simulated device has been + * removed from the repository. + */ + public void clean() { + + } + + public String getId() { + return id; + } + + public Status getStatus() { + return status; + } + + public double getProgress() { + return progress; + } + + public String getTenant() { + return tenant; + } + + public void setId(final String id) { + this.id = id; + } + + public void setTenant(final String tenant) { + this.tenant = tenant; + } + + public void setStatus(final Status status) { + this.status = status; + } + + public void setProgress(final double progress) { + this.progress = progress; + } + + public String getSwversion() { + return swversion; + } + + public void setSwversion(final String swversion) { + this.swversion = swversion; + } + + public ResponseStatus getResponseStatus() { + return responseStatus; + } + + public void setResponseStatus(final ResponseStatus responseStatus) { + this.responseStatus = responseStatus; + } + + public Protocol getProtocol() { + return protocol; + } + + public int getNextPollCounterSec() { + return nextPollCounterSec; + } + + public void setNextPollCounterSec(final int nextPollDelayInSec) { + this.nextPollCounterSec = nextPollDelayInSec; + } +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DDISimulatedDevice.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DDISimulatedDevice.java new file mode 100644 index 000000000..1417c3153 --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DDISimulatedDevice.java @@ -0,0 +1,111 @@ +/** + * 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.simulator; + +import java.util.concurrent.ScheduledExecutorService; + +import org.eclipse.hawkbit.simulator.DeviceSimulatorUpdater.UpdaterCallback; +import org.eclipse.hawkbit.simulator.http.ControllerResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.PathNotFoundException; + +/** + * @author Michael Hirsch + * + */ +public class DDISimulatedDevice extends AbstractSimulatedDevice { + + private static final Logger LOGGER = LoggerFactory.getLogger(DDISimulatedDevice.class); + + private final int pollDelaySec; + private final ScheduledExecutorService pollthreadpool; + private final ControllerResource controllerResource; + + private volatile boolean removed; + private volatile Long currentActionId; + private final DeviceSimulatorUpdater deviceUpdater; + + /** + * @param id + * the ID of the device + * @param tenant + * the tenant of the simulated device + * @param pollDelaySec + * the delay of the poll interval in sec + * @param controllerResource + * the http controller resource + * @param pollthreadpool + * the threadpool for polling endpoint + * @param deviceUpdater + * the service to update devices + */ + public DDISimulatedDevice(final String id, final String tenant, final int pollDelaySec, + final ControllerResource controllerResource, final ScheduledExecutorService pollthreadpool, + final DeviceSimulatorUpdater deviceUpdater) { + super(id, tenant, Protocol.DDI_HTTP); + this.pollDelaySec = pollDelaySec; + this.controllerResource = controllerResource; + this.pollthreadpool = pollthreadpool; + this.deviceUpdater = deviceUpdater; + setNextPollCounterSec(pollDelaySec); + } + + @Override + public void clean() { + super.clean(); + removed = true; + } + + public int getPollDelaySec() { + return pollDelaySec; + } + + /** + * Polls the base URL for the DDI API interface. + */ + public void poll() { + if (!removed) { + final String basePollJson = controllerResource.get(getTenant(), getId()); + try { + final String href = JsonPath.parse(basePollJson).read("_links.deploymentBase.href"); + final long actionId = Long.parseLong(href.substring(href.lastIndexOf("/") + 1, href.indexOf("?"))); + if (currentActionId == null) { + final String deploymentJson = controllerResource.getDeployment(getTenant(), getId(), actionId); + final String swVersion = JsonPath.parse(deploymentJson).read("deployment.chunks[0].version"); + currentActionId = actionId; + deviceUpdater.startUpdate(getTenant(), getId(), actionId, swVersion, new UpdaterCallback() { + @Override + public void updateFinished(final AbstractSimulatedDevice device, final Long actionId) { + switch (device.getResponseStatus()) { + case SUCCESSFUL: + controllerResource.postSuccessFeedback(getTenant(), getId(), actionId); + break; + case ERROR: + controllerResource.postErrorFeedback(getTenant(), getId(), actionId); + break; + default: + throw new IllegalStateException("simulated device has an unknown response status + " + + device.getResponseStatus()); + } + currentActionId = null; + } + }); + } + } catch (final PathNotFoundException e) { + // href might not be in the json response, so ignore + // exception here. + LOGGER.trace("Response does not contain a deploymentbase href link, ignoring."); + } + + } + } +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DMFSimulatedDevice.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DMFSimulatedDevice.java new file mode 100644 index 000000000..b9fdc827c --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DMFSimulatedDevice.java @@ -0,0 +1,29 @@ +/** + * 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.simulator; + +/** + * An simulated device using the DMF API of the hawkbit update server. + * + * @author Michael Hirsch + * + */ +public class DMFSimulatedDevice extends AbstractSimulatedDevice { + + /** + * @param id + * the ID of the device + * @param tenant + * the tenant of the simulated device + */ + public DMFSimulatedDevice(final String id, final String tenant) { + super(id, tenant, Protocol.DMF_AMQP); + } + +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulator.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulator.java index 37812a19d..944ba1d07 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulator.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulator.java @@ -8,8 +8,15 @@ */ package org.eclipse.hawkbit.simulator; +import java.util.concurrent.Executors; + import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +import com.google.common.eventbus.AsyncEventBus; +import com.google.common.eventbus.EventBus; +import com.vaadin.spring.annotation.EnableVaadin; /** * The main-method to start the Spring-Boot application. @@ -18,12 +25,21 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; * */ @SpringBootApplication +@EnableVaadin public class DeviceSimulator { - private DeviceSimulator() { + public DeviceSimulator() { // utility class } + /** + * @return an asynchronous event bus to publish and retrieve events. + */ + @Bean + public EventBus eventBus() { + return new AsyncEventBus(Executors.newFixedThreadPool(4)); + } + /** * Start the Spring Boot Application. * diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorRepository.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorRepository.java new file mode 100644 index 000000000..68db9df45 --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorRepository.java @@ -0,0 +1,123 @@ +/** + * 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.simulator; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * An in-memory simulated device repository to hold the simulated device in + * memory and be able to retrieve them again. + * + * @author Michael Hirsch + * + */ +@Service +public class DeviceSimulatorRepository { + + private final Map devices = new LinkedHashMap<>(); + + @Autowired + private SimulatedDeviceFactory deviceFactory; + + /** + * Adds a simulated device to the repository. + * + * @param simulatedDevice + * the device to add + * @return the device which has been added to the repository + */ + public AbstractSimulatedDevice add(final AbstractSimulatedDevice simulatedDevice) { + devices.put(new DeviceKey(simulatedDevice.getTenant().toLowerCase(), simulatedDevice.getId()), simulatedDevice); + return simulatedDevice; + } + + /** + * @return all simulated devices + */ + public Collection getAll() { + return devices.values(); + } + + /** + * Retrieves a single simulated devices or {@code null} if device does not + * exists. + * + * @param tenant + * the tenant of the simulated device + * @param id + * the ID of the device + * @return a simulated device from the repository or {@code null} if device + * does not exixts. + */ + public AbstractSimulatedDevice get(final String tenant, final String id) { + return devices.get(new DeviceKey(tenant.toLowerCase(), id)); + } + + /** + * Clears all stored devices. + */ + public void clear() { + devices.values().forEach(device -> device.clean()); + devices.clear(); + } + + private static final class DeviceKey { + private final String tenant; + private final String id; + + private DeviceKey(final String tenant, final String id) { + this.tenant = tenant; + this.id = id; + } + + @Override + public int hashCode() {// NOSONAR - as this is generated + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((tenant == null) ? 0 : tenant.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) {// NOSONAR - as this is + // generated + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final DeviceKey other = (DeviceKey) obj; + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + if (tenant == null) { + if (other.tenant != null) { + return false; + } + } else if (!tenant.equals(other.tenant)) { + return false; + } + return true; + } + } +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java new file mode 100644 index 000000000..6e93b7d04 --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java @@ -0,0 +1,122 @@ +/** + * 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.simulator; + +import java.util.Random; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.eclipse.hawkbit.simulator.amqp.SpSenderService; +import org.eclipse.hawkbit.simulator.event.InitUpdate; +import org.eclipse.hawkbit.simulator.event.ProgressUpdate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.google.common.eventbus.EventBus; + +/** + * @author Michael Hirsch + * + */ +@Service +public class DeviceSimulatorUpdater { + + private static final ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(4); + + @Autowired + private SpSenderService spSenderService; + + @Autowired + private EventBus eventbus; + + @Autowired + private DeviceSimulatorRepository repository; + + /** + * Starting an simulated update process of an simulated device. + * + * @param tenant + * the tenant of the device + * @param id + * the ID of the simulated device + * @param actionId + * the actionId from the hawkbit update server to start the + * update. + * @param swVersion + * the software module version from the hawkbit update server + * @param callback + * the callback which gets called when the simulated update + * process has been finished + */ + public void startUpdate(final String tenant, final String id, final long actionId, final String swVersion, + final UpdaterCallback callback) { + final AbstractSimulatedDevice device = repository.get(tenant, id); + device.setProgress(0.0); + device.setSwversion(swVersion); + eventbus.post(new InitUpdate(device)); + threadPool.schedule(new DeviceSimulatorUpdateThread(device, spSenderService, actionId, eventbus, callback), + 2000, TimeUnit.MILLISECONDS); + } + + private static final class DeviceSimulatorUpdateThread implements Runnable { + private static final Random rndSleep = new Random(); + + private final AbstractSimulatedDevice device; + private final SpSenderService spSenderService; + private final long actionId; + private final EventBus eventbus; + private final UpdaterCallback callback; + + private DeviceSimulatorUpdateThread(final AbstractSimulatedDevice device, + final SpSenderService spSenderService, final long actionId, final EventBus eventbus, + final UpdaterCallback callback) { + this.device = device; + this.spSenderService = spSenderService; + this.actionId = actionId; + this.eventbus = eventbus; + this.callback = callback; + } + + @Override + public void run() { + final double newProgress = device.getProgress() + 0.2; + device.setProgress(newProgress); + if (newProgress < 1.0) { + threadPool.schedule(new DeviceSimulatorUpdateThread(device, spSenderService, actionId, eventbus, + callback), rndSleep.nextInt(3000), TimeUnit.MILLISECONDS); + } else { + callback.updateFinished(device, actionId); + } + eventbus.post(new ProgressUpdate(device)); + } + } + + /** + * Callback interface which is called when the simulated update process has + * been finished and the caller of starting the simulated update process can + * send the result to the hawkbit update server back. + * + * @author Michael Hirsch + * + */ + @FunctionalInterface + public interface UpdaterCallback { + /** + * Callback method to indicate that the simulated update process has + * been finished. + * + * @param device + * the device which has been updated + * @param actionId + * the ID of the action from the hawkbit update server + */ + void updateFinished(AbstractSimulatedDevice device, final Long actionId); + } +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/NextPollTimeController.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/NextPollTimeController.java new file mode 100644 index 000000000..81acf897e --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/NextPollTimeController.java @@ -0,0 +1,78 @@ +/** + * 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.simulator; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import org.eclipse.hawkbit.simulator.event.NextPollCounterUpdate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.google.common.eventbus.EventBus; + +/** + * Poll time trigger which executes the {@link DDISimulatedDevice#poll()} every + * second. + * + * @author Michael Hirsch + * + */ +@Component +public class NextPollTimeController { + + private static final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); + private static final ExecutorService pollService = Executors.newFixedThreadPool(1); + + @Autowired + private DeviceSimulatorRepository repository; + + @Autowired + private EventBus eventBus; + + /** + * Constructor which schedules the poll trigger runnable every second. + */ + public NextPollTimeController() { + executorService.scheduleWithFixedDelay(new NextPollUpdaterRunnable(), 1, 1, TimeUnit.SECONDS); + } + + private class NextPollUpdaterRunnable implements Runnable { + @Override + public void run() { + final List devices = repository.getAll().stream() + .filter(device -> device instanceof DDISimulatedDevice).collect(Collectors.toList()); + + devices.forEach(device -> { + int nextCounter = device.getNextPollCounterSec() - 1; + if (nextCounter < 0) { + if (device instanceof DDISimulatedDevice) { + try { + pollService.submit(new Runnable() { + @Override + public void run() { + ((DDISimulatedDevice) device).poll(); + } + }); + } catch (final Exception e) { + + } + nextCounter = ((DDISimulatedDevice) device).getPollDelaySec(); + } + } + device.setNextPollCounterSec(nextCounter); + }); + eventBus.post(new NextPollCounterUpdate(devices)); + } + } +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulatedDeviceFactory.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulatedDeviceFactory.java new file mode 100644 index 000000000..d3e080806 --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulatedDeviceFactory.java @@ -0,0 +1,88 @@ +/** + * 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.simulator; + +import java.net.URL; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol; +import org.eclipse.hawkbit.simulator.http.ControllerResource; +import org.eclipse.hawkbit.simulator.http.GatewayTokenInterceptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import feign.Feign; +import feign.Logger; + +/** + * The simulated device factory to create either {@link DMFSimulatedDevice} or + * {@link DDISimulatedDevice#}. + * + * @author Michael Hirsch + * + */ +@Service +public class SimulatedDeviceFactory { + + private static final ScheduledExecutorService pollThreadPool = Executors.newScheduledThreadPool(4); + + @Autowired + private DeviceSimulatorUpdater deviceUpdater; + + /** + * Creating a simulated devices. + * + * @param id + * the ID of the simulated device + * @param tenant + * the tenant of the simulated device + * @param protocol + * the protocol of the device + * @return the created simulated device + */ + public AbstractSimulatedDevice createSimulatedDevice(final String id, final String tenant, final Protocol protocol) { + return createSimulatedDevice(id, tenant, protocol, 30, null, null); + } + + /** + * Creating a simulated device. + * + * @param id + * the ID of the simulated device + * @param tenant + * the tenant of the simulated device + * @param protocol + * the protocol which should be used be the simulated device + * @param pollDelaySec + * the poll delay time in seconds which should be used for + * {@link DDISimulatedDevice}s + * @param baseEndpoint + * the http base endpoint which should be used for + * {@link DDISimulatedDevice}s + * @param gatewayToken + * the gatewayToken to be used to authenticate + * {@link DDISimulatedDevice}s at the endpoint + * @return the created simulated device + */ + public AbstractSimulatedDevice createSimulatedDevice(final String id, final String tenant, final Protocol protocol, + final int pollDelaySec, final URL baseEndpoint, final String gatewayToken) { + switch (protocol) { + case DMF_AMQP: + return new DMFSimulatedDevice(id, tenant); + case DDI_HTTP: + final ControllerResource controllerResource = Feign.builder().logger(new Logger.ErrorLogger()) + .requestInterceptor(new GatewayTokenInterceptor(gatewayToken)).logLevel(Logger.Level.BASIC) + .target(ControllerResource.class, baseEndpoint.toString()); + return new DDISimulatedDevice(id, tenant, pollDelaySec, controllerResource, pollThreadPool, deviceUpdater); + default: + throw new IllegalArgumentException("Protocol " + protocol + " unknown"); + } + } +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java index ab8847cb1..6f94bd319 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java @@ -8,6 +8,7 @@ */ package org.eclipse.hawkbit.simulator; +import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol; import org.eclipse.hawkbit.simulator.amqp.SpSenderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; @@ -26,6 +27,12 @@ public class SimulationController { @Autowired private SpSenderService spSenderService; + @Autowired + private DeviceSimulatorRepository repository; + + @Autowired + private SimulatedDeviceFactory deviceFactory; + /** * The start resource to start a device creation. * @@ -43,7 +50,9 @@ public class SimulationController { @RequestParam(value = "tenant", defaultValue = "DEFAULT") final String tenant) { for (int i = 0; i < amount; i++) { - spSenderService.createOrUpdateThing(tenant, name + i); + final String deviceId = name + i; + repository.add(deviceFactory.createSimulatedDevice(deviceId, tenant, Protocol.DMF_AMQP)); + spSenderService.createOrUpdateThing(tenant, deviceId); } return "Updated " + amount + " DMF connected targets!"; diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpConfiguration.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpConfiguration.java index c4968c849..492bb3857 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpConfiguration.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpConfiguration.java @@ -132,6 +132,9 @@ public class AmqpConfiguration { final SimpleRabbitListenerContainerFactory containerFactory = new SimpleRabbitListenerContainerFactory(); containerFactory.setDefaultRequeueRejected(false); containerFactory.setConnectionFactory(connectionFactory); + containerFactory.setConcurrentConsumers(20); + containerFactory.setMaxConcurrentConsumers(20); + containerFactory.setPrefetchCount(20); return containerFactory; } diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpReceiverService.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpReceiverService.java index 5884a2645..6f0ac732e 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpReceiverService.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpReceiverService.java @@ -15,6 +15,9 @@ import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey; import org.eclipse.hawkbit.dmf.amqp.api.MessageType; import org.eclipse.hawkbit.dmf.json.model.ActionStatus; import org.eclipse.hawkbit.dmf.json.model.DownloadAndUpdateRequest; +import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice; +import org.eclipse.hawkbit.simulator.DeviceSimulatorUpdater; +import org.eclipse.hawkbit.simulator.DeviceSimulatorUpdater.UpdaterCallback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.core.Message; @@ -40,6 +43,8 @@ public class SpReceiverService extends ReceiverService { private final SpSenderService spSenderService; + private final DeviceSimulatorUpdater deviceUpdater; + /** * Constructor. * @@ -51,12 +56,15 @@ public class SpReceiverService extends ReceiverService { * the lwm2mSenderService * @param spSenderService * the spSenderService + * @param deviceUpdater + * the updater service to simulate update process */ @Autowired public SpReceiverService(final RabbitTemplate rabbitTemplate, final AmqpProperties amqpProperties, - final SpSenderService spSenderService) { + final SpSenderService spSenderService, final DeviceSimulatorUpdater deviceUpdater) { super(rabbitTemplate, amqpProperties); this.spSenderService = spSenderService; + this.deviceUpdater = deviceUpdater; } @@ -139,16 +147,23 @@ public class SpReceiverService extends ReceiverService { spSenderService.sendActionStatusMessage(tenant, ActionStatus.RUNNING, "device Simulator retrieved update request. proceeding with simulation.", actionId); - - final SimulatedUpdate update = new SimulatedUpdate(tenant, thingId, actionId); - - try { - Thread.sleep(1_000); - } catch (final InterruptedException e) { - LOGGER.error("Sleep interrupted", e); - } - - spSenderService.finishUpdateProcess(update, "Simulation complete!"); + deviceUpdater.startUpdate(tenant, thingId, actionId, downloadAndUpdateRequest.getSoftwareModules().get(0) + .getModuleVersion(), new UpdaterCallback() { + @Override + public void updateFinished(final AbstractSimulatedDevice device, final Long actionId) { + switch (device.getResponseStatus()) { + case SUCCESSFUL: + spSenderService.finishUpdateProcess(new SimulatedUpdate(device.getTenant(), device.getId(), + actionId), "Simulation complete!"); + break; + case ERROR: + spSenderService.finishUpdateProcessWithError(new SimulatedUpdate(device.getTenant(), + device.getId(), actionId), "Simulation complete with error!"); + break; + default: + break; + } + } + }); } - } diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/InitUpdate.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/InitUpdate.java new file mode 100644 index 000000000..1cf6dbbda --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/InitUpdate.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.simulator.event; + +import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice; + +/** + * Event description which indicates the initialization of an update. + * + * @author Michael Hirsch + * + */ +public class InitUpdate { + + private final AbstractSimulatedDevice device; + + /** + * Creates new progress update event. + * + * @param device + * the device which progress has been updated + */ + public InitUpdate(final AbstractSimulatedDevice device) { + this.device = device; + } + + /** + * @return the device of the event + */ + public AbstractSimulatedDevice getDevice() { + return device; + } + +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/NextPollCounterUpdate.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/NextPollCounterUpdate.java new file mode 100644 index 000000000..b9d7b9027 --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/NextPollCounterUpdate.java @@ -0,0 +1,42 @@ +/** + * 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.simulator.event; + +import java.util.List; + +import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice; + +/** + * Event description which indicates an poll time update. + * + * @author Michael Hirsch + * + */ +public class NextPollCounterUpdate { + + private final List devices; + + /** + * Creates poll timer update event. + * + * @param devices + * the devices which progress has been updated + */ + public NextPollCounterUpdate(final List devices) { + this.devices = devices; + } + + /** + * @return the devices of the event + */ + public List getDevices() { + return devices; + } + +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/ProgressUpdate.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/ProgressUpdate.java new file mode 100644 index 000000000..3e34a0fa1 --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/ProgressUpdate.java @@ -0,0 +1,41 @@ +/** + * 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.simulator.event; + +import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice; + +/** + * Event definition object which is published if the simulated device updated + * its update progress. + * + * @author Michael Hirsch + * + */ +public class ProgressUpdate { + + private final AbstractSimulatedDevice device; + + /** + * Creates new progress update event. + * + * @param device + * the device which progress has been updated + */ + public ProgressUpdate(final AbstractSimulatedDevice device) { + this.device = device; + } + + /** + * @return the device of the event + */ + public AbstractSimulatedDevice getDevice() { + return device; + } + +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/http/ControllerResource.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/http/ControllerResource.java new file mode 100644 index 000000000..1dac4c80b --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/http/ControllerResource.java @@ -0,0 +1,88 @@ +/** + * 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.simulator.http; + +import org.eclipse.hawkbit.simulator.DDISimulatedDevice; + +import feign.Body; +import feign.Headers; +import feign.Param; +import feign.RequestLine; + +/** + * A feign based controller resource interface declaration for + * {@link DDISimulatedDevice}s using over HTTP. + * + * @author Michael Hirsch + * + */ +public interface ControllerResource { + + /** + * The base poll URL for the devices to retrieve if there is an update + * available. + * + * @param tenant + * the tenant of the device + * @param controllerId + * the ID of the device + * @return the plain json response of the http request + */ + @RequestLine("GET /{tenant}/controller/v1/{controllerId}") + @Headers({ "Content-Type: application/json" }) + String get(@Param("tenant") final String tenant, @Param("controllerId") final String controllerId); + + /** + * Retrieving the deployment job response from the hawkbit update server. + * + * @param tenant + * the tenant for the simulated device + * @param controllerId + * the ID of the device + * @param actionId + * the ID of the action to retrieve + * @return the json response of the http request + */ + @RequestLine("GET /{tenant}/controller/v1/{controllerId}/deploymentBase/{actionId}") + @Headers({ "Content-Type: application/json" }) + String getDeployment(@Param("tenant") final String tenant, @Param("controllerId") final String controllerId, + @Param("actionId") final long actionId); + + /** + * Post a success update feedback to the hawkbit update server + * + * @param tenant + * the tenant of the device + * @param controllerId + * the ID of the device + * @param actionId + * the ID of the action to post feedback back + */ + @RequestLine("POST /{tenant}/controller/v1/{controllerId}/deploymentBase/{actionId}/feedback") + @Headers("Content-Type: application/json") + @Body("%7B\"id\":{actionId},\"time\":\"20140511T121314\",\"status\":%7B\"execution\":\"closed\",\"result\":%7B\"finished\":\"success\",\"progress\":%7B%7D%7D%7D%7D") + void postSuccessFeedback(@Param("tenant") final String tenant, @Param("controllerId") final String controllerId, + @Param("actionId") final long actionId); + + /** + * Post a failure update feedback to the hawkbit update server + * + * @param tenant + * the tenant of the device + * @param controllerId + * the ID of the device + * @param actionId + * the ID of the action to post feedback back + */ + @RequestLine("POST /{tenant}/controller/v1/{controllerId}/deploymentBase/{actionId}/feedback") + @Headers("Content-Type: application/json") + @Body("%7B\"id\":{actionId},\"time\":\"20140511T121314\",\"status\":%7B\"execution\":\"closed\",\"result\":%7B\"finished\":\"failure\",\"progress\":%7B%7D%7D%7D%7D") + void postErrorFeedback(@Param("tenant") final String tenant, @Param("controllerId") final String controllerId, + @Param("actionId") final long actionId); +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/http/GatewayTokenInterceptor.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/http/GatewayTokenInterceptor.java new file mode 100644 index 000000000..3381481de --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/http/GatewayTokenInterceptor.java @@ -0,0 +1,36 @@ +/** + * 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.simulator.http; + +import feign.RequestInterceptor; +import feign.RequestTemplate; + +/** + * A feign interceptor to apply the gateway-token header to each http-request. + * + * @author Michael Hirsch + * + */ +public class GatewayTokenInterceptor implements RequestInterceptor { + + private final String gatewayToken; + + /** + * @param gatewayToken + * the gatwway token to be used in the http-header + */ + public GatewayTokenInterceptor(final String gatewayToken) { + this.gatewayToken = gatewayToken; + } + + @Override + public void apply(final RequestTemplate template) { + template.header("Authorization", "GatewayToken " + gatewayToken); + } +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java new file mode 100644 index 000000000..1400ec0e1 --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java @@ -0,0 +1,233 @@ +/** + * 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.simulator.ui; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.vaadin.data.Property.ValueChangeEvent; +import com.vaadin.data.Property.ValueChangeListener; +import com.vaadin.data.util.ObjectProperty; +import com.vaadin.data.validator.NullValidator; +import com.vaadin.data.validator.RangeValidator; +import com.vaadin.data.validator.RegexpValidator; +import com.vaadin.server.FontAwesome; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Button.ClickListener; +import com.vaadin.ui.FormLayout; +import com.vaadin.ui.OptionGroup; +import com.vaadin.ui.TextField; +import com.vaadin.ui.Window; + +/** + * Popup dialog window for setting the values of generating the simulated + * devices, e.g. the amount. + * + * @author Michael Hirsch + * + */ +public class GenerateDialog extends Window { + + private static final long serialVersionUID = 1L; + private static final Logger LOGGER = LoggerFactory.getLogger(GenerateDialog.class); + + private final FormLayout formLayout = new FormLayout(); + + /** + * Creates a new pop window for setting the configuration of simulating + * devices. + * + * @param callback + * the callback which is called when the dialog has been + * successfully confirmed. + */ + public GenerateDialog(final GenerateDialogCallback callback) { + + formLayout.setSpacing(true); + formLayout.setMargin(true); + + final TextField tf1 = new TextField("name prefix", "dmfSimulated"); + tf1.setIcon(FontAwesome.INFO); + tf1.setRequired(true); + tf1.addValidator(new NullValidator("Must be given", false)); + + final TextField tf2 = new TextField("amount", new ObjectProperty(10)); + tf2.setIcon(FontAwesome.GEAR); + tf2.setRequired(true); + tf2.addValidator(new RangeValidator("Must be between 1 and 30000", Integer.class, 1, 30000)); + + final TextField tf3 = new TextField("tenant", "default"); + tf3.setIcon(FontAwesome.USER); + tf3.setRequired(true); + tf3.addValidator(new NullValidator("Must be given", false)); + + final TextField tf4 = new TextField("poll delay (sec)", new ObjectProperty(10)); + tf4.setIcon(FontAwesome.CLOCK_O); + tf4.setRequired(true); + tf4.setVisible(false); + tf4.addValidator(new RangeValidator("Must be between 1 and 60", Integer.class, 1, 60)); + + final TextField tf5 = new TextField("base poll URL endpoint", "http://localhost:8080"); + tf5.setColumns(50); + tf5.setIcon(FontAwesome.FLAG_O); + tf5.setRequired(true); + tf5.setVisible(false); + tf5.addValidator(new RegexpValidator( + "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]", "is not an URL")); + + final TextField tf6 = new TextField("gateway token", ""); + tf6.setColumns(50); + tf6.setIcon(FontAwesome.FLAG_O); + tf6.setRequired(true); + tf6.setVisible(false); + + final OptionGroup protocolGroup = new OptionGroup("Simulated Device Protocol"); + protocolGroup.addItem(Protocol.DMF_AMQP); + protocolGroup.addItem(Protocol.DDI_HTTP); + protocolGroup.setItemCaption(Protocol.DMF_AMQP, "Device Management Federation API (AMQP push)"); + protocolGroup.setItemCaption(Protocol.DDI_HTTP, "Direct Device Interface (HTTP poll)"); + protocolGroup.setNullSelectionAllowed(false); + protocolGroup.select(Protocol.DMF_AMQP); + protocolGroup.addValueChangeListener(new ValueChangeListener() { + private static final long serialVersionUID = 1L; + + @Override + public void valueChange(final ValueChangeEvent event) { + if (event.getProperty().getValue().equals(Protocol.DDI_HTTP)) { + tf4.setVisible(true); + tf5.setVisible(true); + tf6.setVisible(true); + } else { + tf4.setVisible(false); + tf5.setVisible(false); + tf6.setVisible(false); + } + } + }); + + final Button buttonOk = new Button("generate"); + buttonOk.setImmediate(true); + buttonOk.setIcon(FontAwesome.GEARS); + buttonOk.addClickListener(new ClickListener() { + private static final long serialVersionUID = 1L; + + @Override + public void buttonClick(final ClickEvent event) { + try { + callback.okButton(tf1.getValue(), tf3.getValue(), Integer.valueOf(tf2.getValue().replace(".", "")), + Integer.valueOf(tf4.getValue().replace(".", "")), new URL(tf5.getValue()), tf6.getValue(), + (Protocol) protocolGroup.getValue()); + } catch (final NumberFormatException e) { + LOGGER.info(e.getMessage(), e); + } catch (final MalformedURLException e) { + LOGGER.info(e.getMessage(), e); + } + GenerateDialog.this.close(); + } + }); + + tf1.addValueChangeListener(event -> checkValid(tf1, tf2, tf3, tf4, buttonOk)); + tf2.addValueChangeListener(event -> checkValid(tf1, tf2, tf3, tf4, buttonOk)); + tf3.addValueChangeListener(event -> checkValid(tf1, tf2, tf3, tf4, buttonOk)); + + formLayout.addComponent(tf1); + formLayout.addComponent(tf2); + formLayout.addComponent(tf3); + formLayout.addComponent(protocolGroup); + formLayout.addComponent(tf4); + formLayout.addComponent(tf5); + formLayout.addComponent(tf6); + formLayout.addComponent(buttonOk); + + setCaption("Simulate Devices"); + setContent(formLayout); + setResizable(false); + center(); + } + + private void checkValid(final TextField tf1, final TextField tf2, final TextField tf3, final TextField tf4, + final Button buttonOk) { + if (tf1.isValid() && tf2.isValid() && tf3.isValid() && tf4.isValid()) { + buttonOk.setEnabled(true); + } else { + buttonOk.setEnabled(false); + } + } + + @Override + public int hashCode() {// NOSONAR - as this is generated + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((formLayout == null) ? 0 : formLayout.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) {// NOSONAR - as this is generated + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final GenerateDialog other = (GenerateDialog) obj; + if (formLayout == null) { + if (other.formLayout != null) { + return false; + } + } else if (!formLayout.equals(other.formLayout)) { + return false; + } + return true; + } + + /** + * Callback interface to retrieve the result from the dialog window. + * + * @author Michael Hirsch + * + */ + @FunctionalInterface + interface GenerateDialogCallback { + /** + * Callback method which is called when dialog closes with the OK + * button. + * + * @param namePrefix + * the parameter for name prefix for the simulated devices + * @param tenant + * the tenant for the simulated devices + * @param amount + * the number of simulated devices to be created + * @param pollDelay + * the delay poll time in seconds for DDI devices + * @param basePollURL + * the base http URL endpoint for DDI devices + * @param gatewayToken + * the gateway token header for authentication for DDI + * devices + * @param protocol + * the protocol to be used for the simulated devices to be + * generated + */ + void okButton(final String namePrefix, final String tenant, final int amount, final int pollDelay, + final URL basePollURL, final String gatewayToken, final Protocol protocol); + } +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorUI.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorUI.java new file mode 100644 index 000000000..1c09a702d --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorUI.java @@ -0,0 +1,62 @@ +/** + * 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.simulator.ui; + +import org.springframework.beans.factory.annotation.Autowired; + +import com.vaadin.annotations.Push; +import com.vaadin.annotations.Theme; +import com.vaadin.annotations.Title; +import com.vaadin.navigator.Navigator; +import com.vaadin.server.VaadinRequest; +import com.vaadin.shared.communication.PushMode; +import com.vaadin.shared.ui.ui.Transport; +import com.vaadin.spring.annotation.SpringUI; +import com.vaadin.spring.navigator.SpringViewProvider; +import com.vaadin.ui.Panel; +import com.vaadin.ui.UI; +import com.vaadin.ui.VerticalLayout; + +/** + * The vaadin simulator UI which allows to generate simulated devices and show + * their current status and update progress. + * + * @author Michael Hirsch + * + */ +@SpringUI(path = "") +@Title("hawkBit Device Simulator") +@Theme(value = "simulator") +@Push(value = PushMode.AUTOMATIC, transport = Transport.WEBSOCKET) +public class SimulatorUI extends UI { + + private static final long serialVersionUID = 1L; + + private final VerticalLayout rootLayout = new VerticalLayout(); + + @Autowired + private SpringViewProvider viewProvider; + + @Override + protected void init(final VaadinRequest request) { + + rootLayout.setSizeFull(); + + final Panel viewContainer = new Panel(); + viewContainer.setSizeFull(); + rootLayout.addComponent(viewContainer); + rootLayout.setExpandRatio(viewContainer, 1.0F); + + final Navigator navigator = new Navigator(this, viewContainer); + navigator.addProvider(viewProvider); + + setContent(rootLayout); + } + +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorView.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorView.java new file mode 100644 index 000000000..25498cea7 --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorView.java @@ -0,0 +1,341 @@ +/** + * 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.simulator.ui; + +import java.net.URL; +import java.util.List; +import java.util.Locale; + +import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice; +import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol; +import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.ResponseStatus; +import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Status; +import org.eclipse.hawkbit.simulator.DeviceSimulatorRepository; +import org.eclipse.hawkbit.simulator.SimulatedDeviceFactory; +import org.eclipse.hawkbit.simulator.amqp.SpSenderService; +import org.eclipse.hawkbit.simulator.event.InitUpdate; +import org.eclipse.hawkbit.simulator.event.NextPollCounterUpdate; +import org.eclipse.hawkbit.simulator.event.ProgressUpdate; +import org.eclipse.hawkbit.simulator.ui.GenerateDialog.GenerateDialogCallback; +import org.springframework.beans.factory.annotation.Autowired; + +import com.google.common.collect.Lists; +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; +import com.vaadin.data.Property.ValueChangeEvent; +import com.vaadin.data.Property.ValueChangeListener; +import com.vaadin.data.util.BeanContainer; +import com.vaadin.data.util.BeanItem; +import com.vaadin.data.util.converter.Converter; +import com.vaadin.navigator.View; +import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent; +import com.vaadin.server.FontAwesome; +import com.vaadin.spring.annotation.SpringView; +import com.vaadin.ui.Button; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.Grid; +import com.vaadin.ui.Grid.CellReference; +import com.vaadin.ui.Grid.CellStyleGenerator; +import com.vaadin.ui.Grid.SelectionMode; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.UI; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.renderers.HtmlRenderer; +import com.vaadin.ui.renderers.ProgressBarRenderer; + +/** + * Vaadin view which allows to generate devices through the DMF API and show the + * current simulated devices in a grid with their current status and update + * progress. + * + * @author Michael Hirsch + * + */ +@SpringView(name = "") +public class SimulatorView extends VerticalLayout implements View { + + private static final long serialVersionUID = 1L; + + @Autowired + private transient SpSenderService spSenderService; + @Autowired + private transient DeviceSimulatorRepository repository; + @Autowired + private transient SimulatedDeviceFactory deviceFactory; + + @Autowired + private transient EventBus eventbus; + + private final Label caption = new Label("DMF/DDI Simulated Devices"); + private final HorizontalLayout toolbar = new HorizontalLayout(); + private final Grid grid = new Grid(); + private final ComboBox responseComboBox = new ComboBox("", Lists.newArrayList(ResponseStatus.SUCCESSFUL, + ResponseStatus.ERROR)); + + private BeanContainer beanContainer; + + @Override + public void enter(final ViewChangeEvent event) { + eventbus.register(this); + setSizeFull(); + + // caption + caption.addStyleName("h2"); + + // toolbar + createToolbar(); + + beanContainer = new BeanContainer<>(AbstractSimulatedDevice.class); + beanContainer.setBeanIdProperty("id"); + + grid.setSizeFull(); + grid.setCellStyleGenerator(new CellStyleGenerator() { + @Override + public String getStyle(final CellReference cellReference) { + return cellReference.getPropertyId().equals("status") ? "centeralign" : null; + } + }); + + grid.setSelectionMode(SelectionMode.NONE); + grid.setContainerDataSource(beanContainer); + grid.appendHeaderRow().getCell("responseStatus").setComponent(responseComboBox); + grid.setColumnOrder("id", "status", "swversion", "progress", "tenant", "protocol", "responseStatus", + "nextPollCounterSec"); + // header widths + grid.getColumn("status").setMaximumWidth(80); + grid.getColumn("protocol").setMaximumWidth(180); + grid.getColumn("responseStatus").setMaximumWidth(240); + grid.getColumn("nextPollCounterSec").setMaximumWidth(210); + + grid.getColumn("nextPollCounterSec").setHeaderCaption("Next Poll in (sec)"); + grid.getColumn("swversion").setHeaderCaption("SW Version"); + grid.getColumn("responseStatus").setHeaderCaption("Response Update Status"); + grid.getColumn("progress").setRenderer(new ProgressBarRenderer()); + grid.getColumn("protocol").setConverter(new Converter() { + @Override + public Protocol convertToModel(final String value, final Class targetType, + final Locale locale) { + return null; + } + + @Override + public String convertToPresentation(final Protocol value, final Class targetType, + final Locale locale) { + switch (value) { + case DDI_HTTP: + return "DDI API (http)"; + case DMF_AMQP: + return "DMF API (amqp)"; + default: + return "unknown"; + } + } + + @Override + public Class getModelType() { + return Protocol.class; + } + + @Override + public Class getPresentationType() { + return String.class; + } + }); + grid.getColumn("status").setRenderer(new HtmlRenderer(), new Converter() { + private static final long serialVersionUID = 1L; + + @Override + public Status convertToModel(final String value, final Class targetType, + final Locale locale) { + return null; + } + + @Override + public String convertToPresentation(final Status value, final Class targetType, + final Locale locale) { + String style = null; + switch (value) { + case UNKNWON: + style = "&#x" + + Integer.toHexString(FontAwesome.QUESTION_CIRCLE.getCodepoint()) + ";"; + break; + case PEDNING: + style = "&#x" + Integer.toHexString(FontAwesome.REFRESH.getCodepoint()) + + ";"; + break; + case FINISH: + style = "&#x" + + Integer.toHexString(FontAwesome.CHECK_CIRCLE.getCodepoint()) + ";"; + break; + case ERROR: + style = "&#x" + + Integer.toHexString(FontAwesome.EXCLAMATION_CIRCLE.getCodepoint()) + ";"; + break; + default: + throw new IllegalStateException("unknown value"); + } + return style; + } + + @Override + public Class getModelType() { + return Status.class; + } + + @Override + public Class getPresentationType() { + return String.class; + } + }); + grid.removeColumn("tenant"); + + // grid combobox + responseComboBox.setItemIcon(ResponseStatus.SUCCESSFUL, FontAwesome.CHECK_CIRCLE); + responseComboBox.setItemIcon(ResponseStatus.ERROR, FontAwesome.EXCLAMATION_CIRCLE); + responseComboBox.setNullSelectionAllowed(false); + responseComboBox.setValue(ResponseStatus.SUCCESSFUL); + responseComboBox.addValueChangeListener(new ValueChangeListener() { + @Override + public void valueChange(final ValueChangeEvent event) { + beanContainer.getItemIds().forEach( + itemId -> beanContainer.getItem(itemId).getItemProperty("responseStatus") + .setValue(event.getProperty().getValue())); + } + }); + + // add all components + addComponent(caption); + addComponent(toolbar); + addComponent(grid); + + setExpandRatio(grid, 1.0F); + + // load beans + repository.getAll().forEach(device -> beanContainer.addBean(device)); + } + + @Override + public void detach() { + super.detach(); + eventbus.unregister(this); + } + + @Subscribe + public void pollCounterUpdate(final NextPollCounterUpdate update) { + final List devices = update.getDevices(); + this.getUI().access(new Runnable() { + @Override + public void run() { + devices.forEach(device -> { + final BeanItem item = beanContainer.getItem(device.getId()); + if (item != null) { + item.getItemProperty("nextPollCounterSec").setValue(device.getNextPollCounterSec()); + } + }); + } + }); + } + + /** + * Method to retrieve {@link InitUpdate} events from the event bus. + * + * @param update + * the update event posted on the event bus + */ + @Subscribe + public void initUpdate(final InitUpdate update) { + final AbstractSimulatedDevice device = update.getDevice(); + this.getUI().access(new Runnable() { + @Override + public void run() { + final BeanItem item = beanContainer.getItem(device.getId()); + if (item != null) { + item.getItemProperty("progress").setValue(device.getProgress()); + item.getItemProperty("status").setValue(Status.PEDNING); + item.getItemProperty("swversion").setValue(device.getSwversion()); + } + + } + }); + } + + /** + * Method to retrieve {@link ProgressUpdate} events from the event bus. + * + * @param update + * the update event posted on the event bus + */ + @Subscribe + public void progessUpdate(final ProgressUpdate update) { + final AbstractSimulatedDevice device = update.getDevice(); + this.getUI().access(new Runnable() { + @Override + public void run() { + final BeanItem item = beanContainer.getItem(device.getId()); + if (item != null) { + item.getItemProperty("progress").setValue(device.getProgress()); + if (device.getProgress() >= 1) { + switch (device.getResponseStatus()) { + case SUCCESSFUL: + item.getItemProperty("status").setValue(Status.FINISH); + break; + case ERROR: + item.getItemProperty("status").setValue(Status.ERROR); + break; + default: + item.getItemProperty("status").setValue(Status.UNKNWON); + } + } else { + item.getItemProperty("status").setValue(Status.PEDNING); + } + } + + } + }); + } + + private void createToolbar() { + final Button createDevicesButton = new Button("generate..."); + createDevicesButton.setIcon(FontAwesome.GEARS); + createDevicesButton.addClickListener(event -> openGenerateDialog()); + + final Button clearDevicesButton = new Button("clear"); + clearDevicesButton.setIcon(FontAwesome.ERASER); + clearDevicesButton.addClickListener(event -> clearSimulatedDevices()); + + toolbar.addComponent(createDevicesButton); + toolbar.addComponent(clearDevicesButton); + toolbar.setSpacing(true); + } + + private void clearSimulatedDevices() { + repository.clear(); + beanContainer.removeAllItems(); + } + + private void openGenerateDialog() { + UI.getCurrent().addWindow(new GenerateDialog(new GenerateDialogCallback() { + @Override + public void okButton(final String namePrefix, final String tenant, final int amount, final int pollDelay, + final URL basePollUrl, final String gatewayToken, final Protocol protocol) { + for (int index = 0; index < amount; index++) { + final String deviceId = namePrefix + index; + beanContainer.addBean(repository.add(deviceFactory.createSimulatedDevice(deviceId, + tenant.toLowerCase(), protocol, pollDelay, basePollUrl, gatewayToken))); + spSenderService.createOrUpdateThing(tenant, deviceId); + } + } + })); + } +} diff --git a/examples/hawkbit-device-simulator/src/main/resources/VAADIN/themes/simulator/styles.scss b/examples/hawkbit-device-simulator/src/main/resources/VAADIN/themes/simulator/styles.scss new file mode 100644 index 000000000..56ed48c34 --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/resources/VAADIN/themes/simulator/styles.scss @@ -0,0 +1,32 @@ +/** + * 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 + */ +// Import valo after setting the parameters +@import "../valo/valo"; + + +.simulator{ + @include valo; + + .yellowicon { + color: orange; + } + .greenicon { + color: green; + } + .grayicon { + color: gray; + } + .redicon { + color: red; + } + + .v-grid-cell.centeralign { + text-align: center; + } +} \ No newline at end of file diff --git a/examples/hawkbit-device-simulator/src/main/resources/application.properties b/examples/hawkbit-device-simulator/src/main/resources/application.properties index 19dd2cb77..402f71bfe 100644 --- a/examples/hawkbit-device-simulator/src/main/resources/application.properties +++ b/examples/hawkbit-device-simulator/src/main/resources/application.properties @@ -25,7 +25,7 @@ spring.rabbitmq.virtualHost=/ spring.rabbitmq.host=localhost spring.rabbitmq.port=5672 spring.rabbitmq.dynamic=true - +spring.rabbitmq.listener.prefetch=100 # SECURITY (SecurityProperties) security.user.name=${BASIC_USERNAME:admin} @@ -44,6 +44,6 @@ security.headers.frame=false security.headers.content-type=false security.headers.hsts=all security.sessions=stateless -security.ignored= +security.ignored=/VAADIN/** server.port=8083 diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/ActionStatusFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/ActionStatusFields.java index f1aa7a8e0..22fa42474 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/ActionStatusFields.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/ActionStatusFields.java @@ -17,10 +17,6 @@ package org.eclipse.hawkbit.repository; */ public enum ActionStatusFields implements FieldNameProvider { - /** - * The type field. - */ - TYPE("type"), /** * The id field. */ diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTest.java index a3533b658..94fa8beca 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTest.java @@ -209,7 +209,7 @@ public abstract class AbstractIntegrationTest implements EnvironmentAware { /* * (non-Javadoc) - * + * * @see org.springframework.context.EnvironmentAware#setEnvironment(org. * springframework.core.env. Environment) */ diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLActionFieldsTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLActionFieldsTest.java new file mode 100644 index 000000000..71cf511ae --- /dev/null +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLActionFieldsTest.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.rsql; + +import static org.fest.assertions.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +import org.eclipse.hawkbit.AbstractIntegrationTest; +import org.eclipse.hawkbit.repository.ActionFields; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.Action.ActionType; +import org.eclipse.hawkbit.repository.model.Target; +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.data.jpa.domain.Specification; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Component Tests - RSQL filtering") +@Stories("RSQL filter actions") +public class RSQLActionFieldsTest extends AbstractIntegrationTest { + + private Target target; + private Action action; + + @Before + public void setupBeforeTest() { + target = new Target("targetId123"); + target.setDescription("targetId123"); + targetManagement.createTarget(target); + action = new Action(); + action.setActionType(ActionType.SOFT); + target.getActions().add(action); + action.setTarget(target); + actionRepository.save(action); + for (int i = 0; i < 10; i++) { + final Action newAction = new Action(); + newAction.setActionType(ActionType.SOFT); + newAction.setActive(i % 2 == 0); + newAction.setTarget(target); + actionRepository.save(newAction); + target.getActions().add(newAction); + } + + } + + @Test + @Description("Test filter action by id") + public void testFilterByParameterId() { + assertRSQLQuery(ActionFields.ID.name() + "==" + action.getId(), 1); + assertRSQLQuery(ActionFields.ID.name() + "==noExist*", 0); + assertRSQLQuery(ActionFields.ID.name() + "=in=(" + action.getId() + ",1000000)", 1); + assertRSQLQuery(ActionFields.ID.name() + "=out=(" + action.getId() + ",1000000)", 10); + } + + @Test + @Description("Test action by status") + public void testFilterByParameterStatus() { + assertRSQLQuery(ActionFields.STATUS.name() + "==pending", 5); + assertRSQLQuery(ActionFields.STATUS.name() + "=in=(pending)", 5); + assertRSQLQuery(ActionFields.STATUS.name() + "=out=(pending)", 6); + + try { + assertRSQLQuery(ActionFields.STATUS.name() + "==true", 5); + fail(); + } catch (final RSQLParameterUnsupportedFieldException e) { + } + } + + private void assertRSQLQuery(final String rsqlParam, final long expectedEntities) { + + final Specification parse = RSQLUtility.parse(rsqlParam, ActionFields.class); + final Slice findEnitity = deploymentManagement.findActionsByTarget(parse, target, + new PageRequest(0, 100)); + final long countAllEntities = deploymentManagement.countActionsByTarget(parse, target); + assertThat(findEnitity).isNotNull(); + assertThat(countAllEntities).isEqualTo(expectedEntities); + } +} diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLDistributionSetFieldTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLDistributionSetFieldTest.java new file mode 100644 index 000000000..014b7a055 --- /dev/null +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLDistributionSetFieldTest.java @@ -0,0 +1,140 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.rsql; + +import static org.fest.assertions.api.Assertions.assertThat; + +import java.util.Arrays; + +import org.eclipse.hawkbit.AbstractIntegrationTest; +import org.eclipse.hawkbit.TestDataUtil; +import org.eclipse.hawkbit.repository.DistributionSetFields; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; +import org.eclipse.hawkbit.repository.model.DistributionSetTag; +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Component Tests - RSQL filtering") +@Stories("RSQL filter distribution set") +public class RSQLDistributionSetFieldTest extends AbstractIntegrationTest { + + @Before + public void seuptBeforeTest() { + + final DistributionSet ds = TestDataUtil.generateDistributionSet("DS", softwareManagement, + distributionSetManagement); + ds.setDescription("DS"); + ds.getMetadata().add(new DistributionSetMetadata("metaKey", ds, "metaValue")); + distributionSetManagement.updateDistributionSet(ds); + + final DistributionSet ds2 = TestDataUtil + .generateDistributionSets("NewDS", 3, softwareManagement, distributionSetManagement).get(0); + + ds2.setDescription("DS2"); + ds2.getMetadata().add(new DistributionSetMetadata("metaKey", ds2, "value")); + distributionSetManagement.updateDistributionSet(ds2); + + final DistributionSetTag targetTag = tagManagement.createDistributionSetTag(new DistributionSetTag("Tag1")); + tagManagement.createDistributionSetTag(new DistributionSetTag("Tag2")); + tagManagement.createDistributionSetTag(new DistributionSetTag("Tag3")); + tagManagement.createDistributionSetTag(new DistributionSetTag("Tag4")); + + distributionSetManagement.assignTag(Arrays.asList(ds.getId(), ds2.getId()), targetTag); + } + + @Test + @Description("Test filter distribution set by id") + public void testFilterByParameterId() { + assertRSQLQuery(DistributionSetFields.ID.name() + "==*", 4); + } + + @Test + @Description("Test filter distribution set by name") + public void testFilterByParameterName() { + assertRSQLQuery(DistributionSetFields.NAME.name() + "==DS", 1); + assertRSQLQuery(DistributionSetFields.NAME.name() + "==*DS", 4); + assertRSQLQuery(DistributionSetFields.NAME.name() + "==noExist*", 0); + assertRSQLQuery(DistributionSetFields.NAME.name() + "=in=(DS,notexist)", 1); + assertRSQLQuery(DistributionSetFields.NAME.name() + "=out=(DS,notexist)", 3); + } + + @Test + @Description("Test filter distribution set by description") + public void testFilterByParameterDescription() { + assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==DS", 1); + assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==DS*", 2); + assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==noExist*", 0); + assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "=in=(DS,notexist)", 1); + assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "=out=(DS,notexist)", 3); + } + + @Test + @Description("Test filter distribution set by version") + public void testFilterByParameterVersion() { + assertRSQLQuery(DistributionSetFields.VERSION.name() + "==v1.0", 2); + assertRSQLQuery(DistributionSetFields.VERSION.name() + "!=v1.0", 2); + assertRSQLQuery(DistributionSetFields.VERSION.name() + "=in=(v1.0,v1.1)", 3); + assertRSQLQuery(DistributionSetFields.VERSION.name() + "=out=(v1.0,error)", 2); + } + + @Test + @Description("Test filter distribution set by complete property") + public void testFilterByAttribute() { + assertRSQLQuery(DistributionSetFields.COMPLETE.name() + "==true", 4); + assertRSQLQuery(DistributionSetFields.COMPLETE.name() + "==noExist*", 0); + assertRSQLQuery(DistributionSetFields.COMPLETE.name() + "=in=(true)", 4); + assertRSQLQuery(DistributionSetFields.COMPLETE.name() + "=out=(true)", 0); + } + + @Test + @Description("Test filter distribution set by tag") + public void testFilterByTag() { + assertRSQLQuery(DistributionSetFields.TAG.name() + "==Tag1", 2); + assertRSQLQuery(DistributionSetFields.TAG.name() + "==T*", 2); + assertRSQLQuery(DistributionSetFields.TAG.name() + "==noExist*", 0); + assertRSQLQuery(DistributionSetFields.TAG.name() + "=in=(Tag1,notexist)", 2); + assertRSQLQuery(DistributionSetFields.TAG.name() + "=out=(Tag1,notexist)", 0); + } + + @Test + @Description("Test filter distribution set by type") + public void testFilterByType() { + assertRSQLQuery(DistributionSetFields.TYPE.name() + "==ecl_os_app_jvm", 4); + assertRSQLQuery(DistributionSetFields.TYPE.name() + "==noExist*", 0); + assertRSQLQuery(DistributionSetFields.TYPE.name() + "=in=(ecl_os_app_jvm,ecl)", 4); + assertRSQLQuery(DistributionSetFields.TYPE.name() + "=out=(ecl_os_app_jvm)", 0); + } + + @Test + @Description("") + public void testFilterByMetadata() { + assertRSQLQuery(DistributionSetFields.METADATA.name() + ".metaKey==metaValue", 1); + assertRSQLQuery(DistributionSetFields.METADATA.name() + ".metaKey==*v*", 2); + assertRSQLQuery(DistributionSetFields.METADATA.name() + ".metaKey==noExist*", 0); + assertRSQLQuery(DistributionSetFields.METADATA.name() + ".metaKey=in=(metaValue,notexist)", 1); + assertRSQLQuery(DistributionSetFields.METADATA.name() + ".metaKey=out=(metaValue,notexist)", 1); + assertRSQLQuery(DistributionSetFields.METADATA.name() + ".notExist==metaValue", 0); + + } + + private void assertRSQLQuery(final String rsqlParam, final long excpectedEntity) { + final Page find = distributionSetManagement.findDistributionSetsAll( + RSQLUtility.parse(rsqlParam, DistributionSetFields.class), new PageRequest(0, 100), false); + final long countAll = find.getTotalElements(); + assertThat(find).isNotNull(); + assertThat(countAll).isEqualTo(excpectedEntity); + } +} diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLDistributionSetMetadataFieldsTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLDistributionSetMetadataFieldsTest.java new file mode 100644 index 000000000..2d113a0a3 --- /dev/null +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLDistributionSetMetadataFieldsTest.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.rsql; + +import static org.fest.assertions.api.Assertions.assertThat; + +import org.eclipse.hawkbit.AbstractIntegrationTest; +import org.eclipse.hawkbit.TestDataUtil; +import org.eclipse.hawkbit.repository.DistributionSetMetadataFields; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Component Tests - RSQL filtering") +@Stories("RSQL filter distribution set metadata") +public class RSQLDistributionSetMetadataFieldsTest extends AbstractIntegrationTest { + + private Long distributionSetId; + + @Before + public void setupBeforeTest() { + final DistributionSet distributionSet = TestDataUtil.generateDistributionSet("DS", softwareManagement, + distributionSetManagement); + distributionSetId = distributionSet.getId(); + for (int i = 0; i < 5; i++) { + final DistributionSetMetadata distributionSetMetadata = new DistributionSetMetadata("" + i, distributionSet, + "" + i); + distributionSet.getMetadata().add(distributionSetMetadata); + } + + distributionSetManagement.updateDistributionSet(distributionSet); + } + + @Test + @Description("Test filter distribution set metadata by key") + public void testFilterByParameterKey() { + assertRSQLQuery(DistributionSetMetadataFields.KEY.name() + "==1", 1); + assertRSQLQuery(DistributionSetMetadataFields.KEY.name() + "!=1", 4); + assertRSQLQuery(DistributionSetMetadataFields.KEY.name() + "=in=(1,2)", 2); + assertRSQLQuery(DistributionSetMetadataFields.KEY.name() + "=out=(1,2)", 3); + } + + @Test + @Description("Test filter distribution set metadata by value") + public void testFilterByParameterValue() { + assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "==1", 1); + assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "!=1", 4); + assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "=in=(1,2)", 2); + assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "=out=(1,2)", 3); + } + + private void assertRSQLQuery(final String rsqlParam, final long expectedEntities) { + + final Page findEnitity = distributionSetManagement + .findDistributionSetMetadataByDistributionSetId(distributionSetId, + RSQLUtility.parse(rsqlParam, DistributionSetMetadataFields.class), new PageRequest(0, 100)); + final long countAllEntities = findEnitity.getTotalElements(); + assertThat(findEnitity).isNotNull(); + assertThat(countAllEntities).isEqualTo(expectedEntities); + } +} diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLSoftwareModuleFieldTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLSoftwareModuleFieldTest.java new file mode 100644 index 000000000..8f2fba6da --- /dev/null +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLSoftwareModuleFieldTest.java @@ -0,0 +1,115 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.rsql; + +import static org.fest.assertions.api.Assertions.assertThat; + +import org.eclipse.hawkbit.AbstractIntegrationTest; +import org.eclipse.hawkbit.repository.SoftwareModuleFields; +import org.eclipse.hawkbit.repository.model.SoftwareModule; +import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata; +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Component Tests - RSQL filtering") +@Stories("RSQL filter software module") +public class RSQLSoftwareModuleFieldTest extends AbstractIntegrationTest { + + @Before + public void seuptBeforeTest() { + + final SoftwareModule ah = softwareManagement + .createSoftwareModule(new SoftwareModule(appType, "agent-hub", "1.0.1", "agent-hub", "")); + softwareManagement.createSoftwareModule(new SoftwareModule(runtimeType, "oracle-jre", "1.7.2", "aa", "")); + softwareManagement.createSoftwareModule(new SoftwareModule(osType, "poky", "3.0.2", "aa", "")); + + final SoftwareModule ah2 = softwareManagement + .createSoftwareModule(new SoftwareModule(appType, "agent-hub2", "1.0.1", "agent-hub2", "")); + + final SoftwareModuleMetadata softwareModuleMetadata = new SoftwareModuleMetadata("metaKey", ah, "metaValue"); + softwareManagement.createSoftwareModuleMetadata(softwareModuleMetadata); + ah.getMetadata().add(softwareModuleMetadata); + softwareManagement.updateSoftwareModule(ah); + + final SoftwareModuleMetadata softwareModuleMetadata2 = new SoftwareModuleMetadata("metaKey", ah2, "value"); + softwareManagement.createSoftwareModuleMetadata(softwareModuleMetadata2); + ah2.getMetadata().add(softwareModuleMetadata2); + softwareManagement.updateSoftwareModule(ah2); + + } + + @Test + @Description("Test filter software module by id") + public void testFilterByParameterId() { + assertRSQLQuery(SoftwareModuleFields.ID.name() + "==*", 4); + } + + @Test + @Description("Test filter software module by name") + public void testFilterByParameterName() { + assertRSQLQuery(SoftwareModuleFields.NAME.name() + "==agent-hub", 1); + assertRSQLQuery(SoftwareModuleFields.NAME.name() + "==agent-hub*", 2); + assertRSQLQuery(SoftwareModuleFields.NAME.name() + "==noExist*", 0); + assertRSQLQuery(SoftwareModuleFields.NAME.name() + "=in=(agent-hub,notexist)", 1); + assertRSQLQuery(SoftwareModuleFields.NAME.name() + "=out=(agent-hub,notexist)", 3); + } + + @Test + @Description("Test filter software module by description") + public void testFilterByParameterDescription() { + assertRSQLQuery(SoftwareModuleFields.DESCRIPTION.name() + "==agent-hub", 1); + assertRSQLQuery(SoftwareModuleFields.DESCRIPTION.name() + "==noExist*", 0); + assertRSQLQuery(SoftwareModuleFields.DESCRIPTION.name() + "=in=(agent-hub,notexist)", 1); + assertRSQLQuery(SoftwareModuleFields.DESCRIPTION.name() + "=out=(agent-hub,notexist)", 3); + } + + @Test + @Description("Test filter software module by version") + public void testFilterByParameterVersion() { + assertRSQLQuery(SoftwareModuleFields.VERSION.name() + "==1.0.1", 2); + assertRSQLQuery(SoftwareModuleFields.VERSION.name() + "!=v1.0", 4); + assertRSQLQuery(SoftwareModuleFields.VERSION.name() + "=in=(1.0.1,1.0.2)", 2); + assertRSQLQuery(SoftwareModuleFields.VERSION.name() + "=out=(1.0.1)", 2); + } + + @Test + @Description("Test filter software module by type") + public void testFilterByType() { + assertRSQLQuery(SoftwareModuleFields.TYPE.name() + "==application", 2); + assertRSQLQuery(SoftwareModuleFields.TYPE.name() + "==noExist*", 0); + assertRSQLQuery(SoftwareModuleFields.TYPE.name() + "=in=(application)", 2); + assertRSQLQuery(SoftwareModuleFields.TYPE.name() + "=out=(application)", 2); + } + + @Test + @Description("") + public void testFilterByMetadata() { + assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".metaKey==metaValue", 1); + assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".metaKey==*v*", 2); + assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".metaKey==noExist*", 0); + assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".metaKey=in=(metaValue,notexist)", 1); + assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".metaKey=out=(metaValue,notexist)", 1); + assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".notExist==metaValue", 0); + + } + + private void assertRSQLQuery(final String rsqlParam, final long excpectedEntity) { + final Page find = softwareManagement.findSoftwareModulesByPredicate( + RSQLUtility.parse(rsqlParam, SoftwareModuleFields.class), new PageRequest(0, 100)); + final long countAll = find.getTotalElements(); + assertThat(find).isNotNull(); + assertThat(countAll).isEqualTo(excpectedEntity); + } +} diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLSoftwareModuleMetadataFieldsTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLSoftwareModuleMetadataFieldsTest.java new file mode 100644 index 000000000..f75893b43 --- /dev/null +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLSoftwareModuleMetadataFieldsTest.java @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.rsql; + +import static org.fest.assertions.api.Assertions.assertThat; + +import org.eclipse.hawkbit.AbstractIntegrationTest; +import org.eclipse.hawkbit.TestDataUtil; +import org.eclipse.hawkbit.repository.SoftwareModuleMetadataFields; +import org.eclipse.hawkbit.repository.model.SoftwareModule; +import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata; +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Component Tests - RSQL filtering") +@Stories("RSQL filter software module metadata") +public class RSQLSoftwareModuleMetadataFieldsTest extends AbstractIntegrationTest { + + private Long softwareModuleId; + + @Before + public void setupBeforeTest() { + final SoftwareModule softwareModule = softwareManagement.createSoftwareModule( + new SoftwareModule(TestDataUtil.findOrCreateSoftwareModuleType(softwareManagement, "application"), + "application", "1.0.0", "Desc", "vendor Limited, California")); + softwareModuleId = softwareModule.getId(); + + for (int i = 0; i < 5; i++) { + final SoftwareModuleMetadata metadata = new SoftwareModuleMetadata("" + i, softwareModule, "" + i); + softwareModule.getMetadata().add(metadata); + softwareModuleMetadataRepository.save(metadata); + } + + softwareManagement.updateSoftwareModule(softwareModule); + } + + @Test + @Description("Test filter software module metadata by key") + public void testFilterByParameterKey() { + assertRSQLQuery(SoftwareModuleMetadataFields.KEY.name() + "==1", 1); + assertRSQLQuery(SoftwareModuleMetadataFields.KEY.name() + "!=1", 4); + assertRSQLQuery(SoftwareModuleMetadataFields.KEY.name() + "=in=(1,2)", 2); + assertRSQLQuery(SoftwareModuleMetadataFields.KEY.name() + "=out=(1,2)", 3); + } + + @Test + @Description("Test fitler software module metadata status by value") + public void testFilterByParameterValue() { + assertRSQLQuery(SoftwareModuleMetadataFields.VALUE.name() + "==1", 1); + assertRSQLQuery(SoftwareModuleMetadataFields.VALUE.name() + "!=1", 4); + assertRSQLQuery(SoftwareModuleMetadataFields.VALUE.name() + "=in=(1,2)", 2); + assertRSQLQuery(SoftwareModuleMetadataFields.VALUE.name() + "=out=(1,2)", 3); + } + + private void assertRSQLQuery(final String rsqlParam, final long expectedEntities) { + + final Page findEnitity = softwareManagement + .findSoftwareModuleMetadataBySoftwareModuleId(softwareModuleId, + RSQLUtility.parse(rsqlParam, SoftwareModuleMetadataFields.class), new PageRequest(0, 100)); + final long countAllEntities = findEnitity.getTotalElements(); + assertThat(findEnitity).isNotNull(); + assertThat(countAllEntities).isEqualTo(expectedEntities); + } +} diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLSoftwareModuleTypeFieldsTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLSoftwareModuleTypeFieldsTest.java new file mode 100644 index 000000000..008de9199 --- /dev/null +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLSoftwareModuleTypeFieldsTest.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.rsql; + +import static org.fest.assertions.api.Assertions.assertThat; + +import org.eclipse.hawkbit.AbstractIntegrationTest; +import org.eclipse.hawkbit.repository.SoftwareModuleTypeFields; +import org.eclipse.hawkbit.repository.model.SoftwareModuleType; +import org.junit.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Component Tests - RSQL filtering") +@Stories("RSQL filter software module test type") +public class RSQLSoftwareModuleTypeFieldsTest extends AbstractIntegrationTest { + + @Test + @Description("Test filter software module test type by id") + public void testFilterByParameterId() { + assertRSQLQuery(SoftwareModuleTypeFields.ID.name() + "==*", 3); + } + + @Test + @Description("Test filter software module test type by name") + public void testFilterByParameterName() { + assertRSQLQuery(SoftwareModuleTypeFields.NAME.name() + "==ECL*", 3); + } + + @Test + @Description("Test filter software module test type by description") + public void testFilterByParameterDescription() { + assertRSQLQuery(SoftwareModuleTypeFields.DESCRIPTION.name() + "==Updated*", 3); + assertRSQLQuery(SoftwareModuleTypeFields.DESCRIPTION.name() + "==noExist*", 0); + } + + @Test + @Description("Test filter software module test type by key") + public void testFilterByParameterKey() { + assertRSQLQuery(SoftwareModuleTypeFields.KEY.name() + "==os", 1); + assertRSQLQuery(SoftwareModuleTypeFields.KEY.name() + "=in=(os)", 1); + assertRSQLQuery(SoftwareModuleTypeFields.KEY.name() + "=out=(os)", 2); + } + + @Test + @Description("Test filter software module test type by max") + public void testFilterByMaxAssignment() { + assertRSQLQuery(SoftwareModuleTypeFields.MAX.name() + "==1", 3); + } + + private void assertRSQLQuery(final String rsqlParam, final long excpectedEntity) { + final Page find = softwareManagement.findSoftwareModuleTypesByPredicate( + RSQLUtility.parse(rsqlParam, SoftwareModuleTypeFields.class), new PageRequest(0, 100)); + final long countAll = find.getTotalElements(); + assertThat(find).isNotNull(); + assertThat(countAll).isEqualTo(excpectedEntity); + } +} diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLTagFieldsTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLTagFieldsTest.java new file mode 100644 index 000000000..e3ab8fc2c --- /dev/null +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLTagFieldsTest.java @@ -0,0 +1,119 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.rsql; + +import static org.fest.assertions.api.Assertions.assertThat; + +import org.eclipse.hawkbit.AbstractIntegrationTest; +import org.eclipse.hawkbit.repository.TagFields; +import org.eclipse.hawkbit.repository.model.DistributionSetTag; +import org.eclipse.hawkbit.repository.model.TargetTag; +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Component Tests - RSQL filtering") +@Stories("RSQL filter target and distribution set tags") +public class RSQLTagFieldsTest extends AbstractIntegrationTest { + + @Before + public void seuptBeforeTest() { + + for (int i = 0; i < 5; i++) { + final TargetTag targetTag = new TargetTag("" + i, "" + i, i % 2 == 0 ? "red" : "blue"); + tagManagement.createTargetTag(targetTag); + final DistributionSetTag distributionSetTag = new DistributionSetTag("" + i, "" + i, + i % 2 == 0 ? "red" : "blue"); + tagManagement.createDistributionSetTag(distributionSetTag); + } + } + + @Test + @Description("Test filter target tag by name") + public void testFilterTargetTagByParameterName() { + assertRSQLQueryTarget(TagFields.NAME.name() + "==1", 1); + assertRSQLQueryTarget(TagFields.NAME.name() + "==*", 5); + assertRSQLQueryTarget(TagFields.NAME.name() + "==noExist*", 0); + assertRSQLQueryTarget(TagFields.NAME.name() + "=in=(1,notexist)", 1); + assertRSQLQueryTarget(TagFields.NAME.name() + "=out=(1,notexist)", 4); + } + + @Test + @Description("Test filter target tag by description") + public void testFilterTargetTagByParameterDescription() { + assertRSQLQueryTarget(TagFields.DESCRIPTION.name() + "==1", 1); + assertRSQLQueryTarget(TagFields.DESCRIPTION.name() + "==*", 5); + assertRSQLQueryTarget(TagFields.DESCRIPTION.name() + "==noExist*", 0); + assertRSQLQueryTarget(TagFields.DESCRIPTION.name() + "=in=(1,notexist)", 1); + assertRSQLQueryTarget(TagFields.DESCRIPTION.name() + "=out=(1,notexist)", 4); + } + + @Test + @Description("Test filter target tag by colour") + public void testFilterTargetTagByParameterColour() { + assertRSQLQueryTarget(TagFields.COLOUR.name() + "==red", 3); + assertRSQLQueryTarget(TagFields.COLOUR.name() + "==r*", 3); + assertRSQLQueryTarget(TagFields.COLOUR.name() + "==noExist*", 0); + assertRSQLQueryTarget(TagFields.COLOUR.name() + "=in=(red,notexist)", 3); + assertRSQLQueryTarget(TagFields.COLOUR.name() + "=out=(red,notexist)", 2); + } + + @Test + @Description("Test filter distribution set tag by name") + public void testFilterDistributionSetTagByParameterName() { + assertRSQLQueryDistributionSet(TagFields.NAME.name() + "==1", 1); + assertRSQLQueryDistributionSet(TagFields.NAME.name() + "==*", 5); + assertRSQLQueryDistributionSet(TagFields.NAME.name() + "==noExist*", 0); + assertRSQLQueryDistributionSet(TagFields.NAME.name() + "=in=(1,2)", 2); + assertRSQLQueryDistributionSet(TagFields.NAME.name() + "=out=(1,2)", 3); + } + + @Test + @Description("Test filter distribution set by description") + public void testFilterDistributionSetTagByParameterDescription() { + assertRSQLQueryDistributionSet(TagFields.DESCRIPTION.name() + "==1", 1); + assertRSQLQueryDistributionSet(TagFields.DESCRIPTION.name() + "==*", 5); + assertRSQLQueryDistributionSet(TagFields.DESCRIPTION.name() + "==noExist*", 0); + assertRSQLQueryDistributionSet(TagFields.DESCRIPTION.name() + "=in=(1,2)", 2); + assertRSQLQueryDistributionSet(TagFields.DESCRIPTION.name() + "=out=(1,2)", 3); + } + + @Test + @Description("Test filter distribution set by colour") + public void testFilterDistributionSetTagByParameterColour() { + assertRSQLQueryDistributionSet(TagFields.COLOUR.name() + "==red", 3); + assertRSQLQueryDistributionSet(TagFields.COLOUR.name() + "==r*", 3); + assertRSQLQueryDistributionSet(TagFields.COLOUR.name() + "==noExist*", 0); + assertRSQLQueryDistributionSet(TagFields.COLOUR.name() + "=in=(red,notexist)", 3); + assertRSQLQueryDistributionSet(TagFields.COLOUR.name() + "=out=(red,notexist)", 2); + } + + private void assertRSQLQueryDistributionSet(final String rsqlParam, final long expectedEntities) { + + final Page findEnitity = tagManagement + .findAllDistributionSetTags(RSQLUtility.parse(rsqlParam, TagFields.class), new PageRequest(0, 100)); + final long countAllEntities = findEnitity.getTotalElements(); + assertThat(findEnitity).isNotNull(); + assertThat(countAllEntities).isEqualTo(expectedEntities); + } + + private void assertRSQLQueryTarget(final String rsqlParam, final long expectedEntities) { + + final Page findEnitity = tagManagement + .findAllTargetTags(RSQLUtility.parse(rsqlParam, TagFields.class), new PageRequest(0, 100)); + final long countAllEntities = findEnitity.getTotalElements(); + assertThat(findEnitity).isNotNull(); + assertThat(countAllEntities).isEqualTo(expectedEntities); + } +} diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLTargetFieldTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLTargetFieldTest.java new file mode 100644 index 000000000..50b6600e9 --- /dev/null +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLTargetFieldTest.java @@ -0,0 +1,171 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.rsql; + +import static org.fest.assertions.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +import java.util.Arrays; + +import org.eclipse.hawkbit.AbstractIntegrationTest; +import org.eclipse.hawkbit.TestDataUtil; +import org.eclipse.hawkbit.repository.TargetFields; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetInfo; +import org.eclipse.hawkbit.repository.model.TargetTag; +import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Component Tests - RSQL filtering") +@Stories("RSQL filter target") +public class RSQLTargetFieldTest extends AbstractIntegrationTest { + + @Before + public void seuptBeforeTest() { + + final DistributionSet ds = TestDataUtil.generateDistributionSet("AssignedDs", softwareManagement, + distributionSetManagement); + + final Target target = new Target("targetId123"); + target.setDescription("targetId123"); + final TargetInfo targetInfo = new TargetInfo(target); + targetInfo.getControllerAttributes().put("revision", "1.1"); + target.setTargetInfo(targetInfo); + target.getTargetInfo().setUpdateStatus(TargetUpdateStatus.PENDING); + + targetManagement.createTarget(target); + final Target target2 = new Target("targetId1234"); + target2.setDescription("targetId1234"); + final TargetInfo targetInfo2 = new TargetInfo(target2); + targetInfo2.getControllerAttributes().put("revision", "1.2"); + target2.setTargetInfo(targetInfo2); + targetManagement.createTarget(target2); + targetManagement.createTarget(new Target("targetId1235")); + targetManagement.createTarget(new Target("targetId1236")); + + final TargetTag targetTag = tagManagement.createTargetTag(new TargetTag("Tag1")); + tagManagement.createTargetTag(new TargetTag("Tag2")); + tagManagement.createTargetTag(new TargetTag("Tag3")); + tagManagement.createTargetTag(new TargetTag("Tag4")); + + targetManagement.assignTag(Arrays.asList(target.getControllerId(), target2.getControllerId()), targetTag); + + deploymentManagement.assignDistributionSet(ds.getId(), target.getControllerId()); + } + + @Test + @Description("Test filter target by (controller) id") + public void testFilterByParameterId() { + assertRSQLQuery(TargetFields.ID.name() + "==targetId123", 1); + assertRSQLQuery(TargetFields.ID.name() + "==target*", 4); + assertRSQLQuery(TargetFields.ID.name() + "==noExist*", 0); + assertRSQLQuery(TargetFields.ID.name() + "=in=(targetId123,notexist)", 1); + assertRSQLQuery(TargetFields.ID.name() + "=out=(targetId123,notexist)", 3); + } + + @Test + @Description("Test filter target by name") + public void testFilterByParameterName() { + assertRSQLQuery(TargetFields.NAME.name() + "==targetId123", 1); + assertRSQLQuery(TargetFields.NAME.name() + "==target*", 4); + assertRSQLQuery(TargetFields.NAME.name() + "==noExist*", 0); + assertRSQLQuery(TargetFields.NAME.name() + "=in=(targetId123,notexist)", 1); + assertRSQLQuery(TargetFields.NAME.name() + "=out=(targetId123,notexist)", 3); + } + + @Test + @Description("Test filter target by description") + public void testFilterByParameterDescription() { + assertRSQLQuery(TargetFields.DESCRIPTION.name() + "==targetId123", 1); + assertRSQLQuery(TargetFields.DESCRIPTION.name() + "==target*", 2); + assertRSQLQuery(TargetFields.DESCRIPTION.name() + "==noExist*", 0); + assertRSQLQuery(TargetFields.DESCRIPTION.name() + "=in=(targetId123,notexist)", 1); + assertRSQLQuery(TargetFields.DESCRIPTION.name() + "=out=(targetId123,notexist)", 1); + } + + @Test + @Description("Test filter target by controller id") + public void testFilterByParameterControllerId() { + assertRSQLQuery(TargetFields.CONTROLLERID.name() + "==targetId123", 1); + assertRSQLQuery(TargetFields.CONTROLLERID.name() + "==target*", 4); + assertRSQLQuery(TargetFields.CONTROLLERID.name() + "==noExist*", 0); + assertRSQLQuery(TargetFields.CONTROLLERID.name() + "=in=(targetId123,notexist)", 1); + assertRSQLQuery(TargetFields.CONTROLLERID.name() + "=out=(targetId123,notexist)", 3); + } + + @Test + @Description("Test filter target by status") + public void testFilterByParameterUpdateStatus() { + assertRSQLQuery(TargetFields.UPDATESTATUS.name() + "==pending", 1); + assertRSQLQuery(TargetFields.UPDATESTATUS.name() + "!=pending", 3); + try { + assertRSQLQuery(TargetFields.UPDATESTATUS.name() + "==noExist*", 0); + fail(); + } catch (final RSQLParameterUnsupportedFieldException e) { + } + assertRSQLQuery(TargetFields.UPDATESTATUS.name() + "=in=(pending,error)", 1); + assertRSQLQuery(TargetFields.UPDATESTATUS.name() + "=out=(pending,error)", 3); + } + + @Test + @Description("Test filter target by attribute") + public void testFilterByAttribute() { + assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".revision==1.1", 1); + assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".revision==1*", 2); + assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".revision==noExist*", 0); + assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".revision=in=(1.1,notexist)", 1); + assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".revision=out=(1.1)", 1); + } + + @Test + @Description("Test filter target by assigned ds name") + public void testFilterByAssignedDsName() { + assertRSQLQuery(TargetFields.ASSIGNEDDS.name() + ".name==AssignedDs", 1); + assertRSQLQuery(TargetFields.ASSIGNEDDS.name() + ".name==A*", 1); + assertRSQLQuery(TargetFields.ASSIGNEDDS.name() + ".name==noExist*", 0); + assertRSQLQuery(TargetFields.ASSIGNEDDS.name() + ".name=in=(AssignedDs,notexist)", 1); + assertRSQLQuery(TargetFields.ASSIGNEDDS.name() + ".name=out=(AssignedDs,notexist)", 0); + } + + @Test + @Description("Test filter target by assigned ds version") + public void testFilterByAssignedDsVersion() { + assertRSQLQuery(TargetFields.ASSIGNEDDS.name() + ".version==v1.0", 1); + assertRSQLQuery(TargetFields.ASSIGNEDDS.name() + ".version==*1*", 1); + assertRSQLQuery(TargetFields.ASSIGNEDDS.name() + ".version==noExist*", 0); + assertRSQLQuery(TargetFields.ASSIGNEDDS.name() + ".version=in=(v1.0,notexist)", 1); + assertRSQLQuery(TargetFields.ASSIGNEDDS.name() + ".version=out=(v1.0,notexist)", 0); + } + + @Test + @Description("Test filter target by tag") + public void testFilterByTag() { + assertRSQLQuery(TargetFields.TAG.name() + "==Tag1", 2); + assertRSQLQuery(TargetFields.TAG.name() + "==T*", 2); + assertRSQLQuery(TargetFields.TAG.name() + "==noExist*", 0); + assertRSQLQuery(TargetFields.TAG.name() + "=in=(Tag1,notexist)", 2); + assertRSQLQuery(TargetFields.TAG.name() + "=out=(Tag1,notexist)", 0); + } + + private void assertRSQLQuery(final String rsqlParam, final long expcetedTargets) { + final Page findTargetPage = targetManagement + .findTargetsAll(RSQLUtility.parse(rsqlParam, TargetFields.class), new PageRequest(0, 100)); + final long countTargetsAll = findTargetPage.getTotalElements(); + assertThat(findTargetPage).isNotNull(); + assertThat(countTargetsAll).isEqualTo(expcetedTargets); + } +} diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/RSQLUtilityTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLUtilityTest.java similarity index 97% rename from hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/RSQLUtilityTest.java rename to hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLUtilityTest.java index c23c52bc5..85092b560 100644 --- a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/RSQLUtilityTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLUtilityTest.java @@ -6,7 +6,7 @@ * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ -package org.eclipse.hawkbit.rest.resource; +package org.eclipse.hawkbit.repository.rsql; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; @@ -31,9 +31,6 @@ import org.eclipse.hawkbit.repository.FieldNameProvider; import org.eclipse.hawkbit.repository.SoftwareModuleFields; import org.eclipse.hawkbit.repository.TargetFields; import org.eclipse.hawkbit.repository.model.SoftwareModule; -import org.eclipse.hawkbit.repository.rsql.RSQLParameterSyntaxException; -import org.eclipse.hawkbit.repository.rsql.RSQLParameterUnsupportedFieldException; -import org.eclipse.hawkbit.repository.rsql.RSQLUtility; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -43,7 +40,7 @@ import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; @RunWith(MockitoJUnitRunner.class) -@Features("Component Tests - Management RESTful API") +@Features("Component Tests - RSQL filtering") @Stories("RSQL search utility") // TODO: fully document tests -> @Description for long text and reasonable // method name as short text