RD: Implemented Device management [WIP]

This commit is contained in:
ipatini 2023-10-06 15:02:23 +03:00
parent dd6312d8f3
commit ce0645e534
14 changed files with 432 additions and 2 deletions

View File

@ -66,6 +66,11 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId> <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
</dependencies> </dependencies>
<dependencyManagement> <dependencyManagement>

View File

@ -0,0 +1,19 @@
package eu.nebulous.resource.discovery;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
public class ResourceDiscoveryConfig {
@Bean
public static ObjectMapper objectMapper() {
return new ObjectMapper()
.registerModule(new JavaTimeModule())
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
}
}

View File

@ -1,6 +1,5 @@
package eu.nebulous.resource.discovery; package eu.nebulous.resource.discovery;
import lombok.Data;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -16,7 +15,6 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.config.Customizer.withDefaults;

View File

@ -0,0 +1,14 @@
package eu.nebulous.resource.discovery.monitor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
//@EnableAsync
//@EnableScheduling
@RequiredArgsConstructor
public class DeviceProcessor {
}

View File

@ -0,0 +1,50 @@
package eu.nebulous.resource.discovery.monitor.controller;
import eu.nebulous.resource.discovery.monitor.model.ArchivedDevice;
import eu.nebulous.resource.discovery.monitor.model.Device;
import eu.nebulous.resource.discovery.monitor.model.DeviceException;
import eu.nebulous.resource.discovery.monitor.service.DeviceManagementService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/monitor/archived")
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
public class ArchivedDeviceManagementController {
private final DeviceManagementService deviceService;
@GetMapping(value = "/device/all", produces = MediaType.APPLICATION_JSON_VALUE)
public List<ArchivedDevice> listDevicesAll() {
return deviceService.getArchivedAll();
}
@GetMapping(value = "/device/owner/{owner}", produces = MediaType.APPLICATION_JSON_VALUE)
public List<ArchivedDevice> listDevicesForOwner(@PathVariable String owner) {
return deviceService.getArchivedByOwner(owner);
}
@GetMapping(value = "/device/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public Device getDevice(@PathVariable String id) {
return deviceService.getArchivedById(id)
.orElseThrow(() -> new DeviceException("Not found archived device with id: "+id));
}
@GetMapping(value = "/device/ipaddress/{ipAddress}", produces = MediaType.APPLICATION_JSON_VALUE)
public List<ArchivedDevice> getDeviceByIpAddress(@PathVariable String ipAddress) {
return deviceService.getArchivedByIpAddress(ipAddress);
}
@GetMapping(value = "/device/{id}/unarchive", produces = MediaType.APPLICATION_JSON_VALUE)
public String unarchiveDevice(@PathVariable String id) {
deviceService.unarchiveDevice(id);
return "UNARCHIVED";
}
}

View File

@ -0,0 +1,72 @@
package eu.nebulous.resource.discovery.monitor.controller;
import eu.nebulous.resource.discovery.monitor.model.Device;
import eu.nebulous.resource.discovery.monitor.model.DeviceException;
import eu.nebulous.resource.discovery.monitor.service.DeviceConversionService;
import eu.nebulous.resource.discovery.monitor.service.DeviceManagementService;
import eu.nebulous.resource.discovery.registration.IRegistrationRequestProcessor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/monitor")
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
public class DeviceManagementController {
private final DeviceManagementService deviceService;
private final DeviceConversionService deviceConversionService;
private final IRegistrationRequestProcessor deviceRequestProcessor;
@GetMapping(value = "/device/all", produces = MediaType.APPLICATION_JSON_VALUE)
public List<Device> listDevicesAll() {
return deviceService.getAll();
}
@GetMapping(value = "/device/owner/{owner}", produces = MediaType.APPLICATION_JSON_VALUE)
public List<Device> listDevicesForOwner(@PathVariable String owner) {
return deviceService.getByOwner(owner);
}
@GetMapping(value = "/device/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public Device getDevice(@PathVariable String id) {
return deviceService.getById(id)
.orElseThrow(() -> new DeviceException("Not found device with id: "+id));
}
@GetMapping(value = "/device/ipaddress/{ipAddress}", produces = MediaType.APPLICATION_JSON_VALUE)
public Device getDeviceByIpAddress(@PathVariable String ipAddress) {
return deviceService.getByIpAddress(ipAddress)
.orElseThrow(() -> new DeviceException("Not found device with IP address: "+ipAddress));
}
@PutMapping(value = "/device", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public Device createDevice(@RequestBody Device device) {
return deviceService.save(device);
}
@PostMapping(value = "/device/{id}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public Device updateDevice(@PathVariable String id, @RequestBody Device device) {
if (! StringUtils.equals(id, device.getId()))
throw new DeviceException(
"Id does not match the id in device: "+id+" <> "+device.getId());
return deviceService.update(device);
}
@DeleteMapping(value = "/device/{id}")
public void deleteDevice(@PathVariable String id) {
deviceService.deleteById(id);
}
@GetMapping(value = "/device/{id}/archive", produces = MediaType.APPLICATION_JSON_VALUE)
public String archiveDevice(@PathVariable String id) {
deviceService.archiveDevice(id);
return "ARCHIVED";
}
}

View File

@ -0,0 +1,9 @@
package eu.nebulous.resource.discovery.monitor.model;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.mongodb.core.mapping.Document;
@Slf4j
@Document(collection = "archived_device")
public class ArchivedDevice extends Device {
}

View File

@ -0,0 +1,41 @@
package eu.nebulous.resource.discovery.monitor.model;
import eu.nebulous.resource.discovery.registration.model.RegistrationRequest;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import org.springframework.data.mongodb.core.mapping.Document;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Data
@SuperBuilder
@NoArgsConstructor
@Document(collection = "device")
public class Device {
private String id;
private String os;
private String name;
private String owner;
private String ipAddress;
private String username;
private char[] password;
private char[] publicKey;
private Map<String, String> deviceInfo;
private RegistrationRequest request;
private String requestId;
private Instant creationDate;
private Instant lastUpdateDate;
private Instant archiveDate;
private DeviceStatus status;
private String nodeReference;
@Setter(AccessLevel.NONE)
private List<String> messages = new ArrayList<>();
}

View File

@ -0,0 +1,8 @@
package eu.nebulous.resource.discovery.monitor.model;
public class DeviceException extends RuntimeException {
public DeviceException(String message) { super(message); }
public DeviceException(Throwable t) { super(t); }
public DeviceException(String message, Throwable t) { super(message, t); }
}

View File

@ -0,0 +1,8 @@
package eu.nebulous.resource.discovery.monitor.model;
public enum DeviceStatus {
NEW_DEVICE, ON_HOLD,
ONBOARDING, ONBOARDED, ONBOARD_ERROR,
HEALTHY, BUSY, IDLE,
OFFBOARDING, OFFBOARDED, OFFBOARD_ERROR
}

View File

@ -0,0 +1,14 @@
package eu.nebulous.resource.discovery.monitor.repository;
import eu.nebulous.resource.discovery.monitor.model.ArchivedDevice;
import eu.nebulous.resource.discovery.monitor.model.Device;
import eu.nebulous.resource.discovery.registration.model.RegistrationRequest;
import org.springframework.data.mongodb.repository.MongoRepository;
import java.util.List;
import java.util.Optional;
public interface ArchivedDeviceRepository extends MongoRepository<ArchivedDevice, String> {
List<ArchivedDevice> findByOwner(String owner);
List<ArchivedDevice> findByIpAddress(String ipAddress);
}

View File

@ -0,0 +1,12 @@
package eu.nebulous.resource.discovery.monitor.repository;
import eu.nebulous.resource.discovery.monitor.model.Device;
import org.springframework.data.mongodb.repository.MongoRepository;
import java.util.List;
import java.util.Optional;
public interface DeviceRepository extends MongoRepository<Device, String> {
List<Device> findByOwner(String owner);
Optional<Device> findByIpAddress(String ipAddress);
}

View File

@ -0,0 +1,26 @@
package eu.nebulous.resource.discovery.monitor.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.nebulous.resource.discovery.monitor.model.ArchivedDevice;
import eu.nebulous.resource.discovery.monitor.model.Device;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class DeviceConversionService {
private final ObjectMapper objectMapper;
public ArchivedDevice toArchivedDevice(@NonNull Device device) {
return objectMapper
.convertValue(device, ArchivedDevice.class);
}
public Device toDevice(@NonNull ArchivedDevice archivedDevice) {
return objectMapper
.convertValue(archivedDevice, Device.class);
}
}

View File

@ -0,0 +1,154 @@
package eu.nebulous.resource.discovery.monitor.service;
import eu.nebulous.resource.discovery.monitor.model.ArchivedDevice;
import eu.nebulous.resource.discovery.monitor.model.Device;
import eu.nebulous.resource.discovery.monitor.model.DeviceException;
import eu.nebulous.resource.discovery.monitor.model.DeviceStatus;
import eu.nebulous.resource.discovery.monitor.repository.ArchivedDeviceRepository;
import eu.nebulous.resource.discovery.monitor.repository.DeviceRepository;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.*;
@Slf4j
@Service
@RequiredArgsConstructor
public class DeviceManagementService {
private final DeviceRepository deviceRepository;
private final ArchivedDeviceRepository archivedDeviceRepository;
private final DeviceConversionService deviceConversionService;
// ------------------------------------------------------------------------
public List<Device> getAll() {
return Collections.unmodifiableList(deviceRepository.findAll());
}
public List<Device> getByOwner(@NonNull String owner) {
return deviceRepository.findByOwner(owner);
}
public Optional<Device> getById(@NonNull String id) {
return deviceRepository.findById(id);
}
public Optional<Device> getByIpAddress(@NonNull String ipAddress) {
return deviceRepository.findByIpAddress(ipAddress);
}
public @NonNull Device save(@NonNull Device device) {
DeviceStatus status = device.getStatus();
if (status == null) {
device.setStatus(DeviceStatus.NEW_DEVICE);
}
if (status != null && status != DeviceStatus.NEW_DEVICE) {
throw new DeviceException("Cannot save a new device with status "+status);
}
if (StringUtils.isBlank(device.getId())) {
device.setId( UUID.randomUUID().toString() );
} else {
throw new DeviceException(
"New device already has an Id: " + device.getId());
}
if (getById(device.getOs()).isPresent())
throw new DeviceException(
"A device with the same Id already exists in repository: "+device.getId());
if (getByIpAddress(device.getIpAddress()).isPresent())
throw new DeviceException(
"A device with the same IP address already exists in repository: "+device.getIpAddress());
device.setCreationDate(Instant.now());
checkDevice(device);
deviceRepository.save(device);
return device;
}
public Device update(@NonNull Device device) {
Optional<Device> result = getById(device.getId());
if (result.isEmpty())
throw new DeviceException(
"Device with the Id does not exists in repository: "+device.getId());
checkDevice(device);
device.setLastUpdateDate(Instant.now());
deviceRepository.save(device);
return getById(device.getId()).orElseThrow(() ->
new DeviceException("Device update failed for Device Id: "+device.getId()));
}
private void checkDevice(@NonNull Device device) {
List<String> errors = new ArrayList<>();
if (StringUtils.isBlank(device.getId())) errors.add("Null or blank Id");
if (StringUtils.isBlank(device.getOwner())) errors.add("Null or blank Owner");
if (device.getCreationDate()==null) errors.add("Null Creation date");
if (device.getStatus()==null) errors.add("Null Status");
if (!errors.isEmpty()) {
throw new DeviceException(
String.format("Device spec has errors: %s\n%s",
String.join(", ", errors), device));
}
}
public void deleteById(@NonNull String id) {
Optional<Device> result = getById(id);
if (result.isEmpty())
throw new DeviceException(
"Device with the Id does not exists in repository: "+id);
deviceRepository.delete(result.get());
result.get().setLastUpdateDate(Instant.now());
}
public void delete(@NonNull Device device) {
deviceRepository.deleteById(device.getId());
device.setLastUpdateDate(Instant.now());
}
// ------------------------------------------------------------------------
public List<ArchivedDevice> getArchivedAll() {
return Collections.unmodifiableList(archivedDeviceRepository.findAll());
}
public List<ArchivedDevice> getArchivedByOwner(@NonNull String owner) {
return archivedDeviceRepository.findByOwner(owner);
}
public Optional<ArchivedDevice> getArchivedById(@NonNull String id) {
return archivedDeviceRepository.findById(id);
}
public List<ArchivedDevice> getArchivedByIpAddress(@NonNull String ipAddress) {
return archivedDeviceRepository.findByIpAddress(ipAddress);
}
public void archiveDevice(String id) {
archiveRequestBySystem(id);
}
public void archiveRequestBySystem(String id) {
Optional<Device> result = getById(id);
if (result.isEmpty())
throw new DeviceException(
"Device with the Id does not exists in repository: " + id);
result.get().setArchiveDate(Instant.now());
archivedDeviceRepository.save(deviceConversionService.toArchivedDevice(result.get()));
deviceRepository.delete(result.get());
}
public void unarchiveDevice(String id) {
Optional<ArchivedDevice> result = getArchivedById(id);
if (result.isEmpty())
throw new DeviceException(
"Archived device with Id does not exists in repository: "+id);
result.get().setArchiveDate(null);
deviceRepository.save(deviceConversionService.toDevice(result.get()));
archivedDeviceRepository.deleteById(result.get().getId());
}
}