feat: 배포된 App 정보 조회

- 장비의 App 목록 조회
- 장비의 Process 목록 조회
Closed: #SCDD-197
This commit is contained in:
hyunjujeong 2023-08-14 15:42:04 +09:00
parent 59cb705c10
commit 9470a02ccb
32 changed files with 762 additions and 68 deletions

View File

@ -23,6 +23,7 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-undertow") implementation("org.springframework.boot:spring-boot-starter-undertow")
implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-validation") implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.boot:spring-boot-starter-amqp")
implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.integration:spring-integration-mqtt") implementation("org.springframework.integration:spring-integration-mqtt")
runtimeOnly("org.postgresql:postgresql") runtimeOnly("org.postgresql:postgresql")

View File

@ -7,6 +7,8 @@ import inc.sdt.blokworks.devicedeployer.domain.OutboundMessage;
import inc.sdt.blokworks.devicedeployer.domain.OperationType; import inc.sdt.blokworks.devicedeployer.domain.OperationType;
import inc.sdt.blokworks.devicedeployer.infrastructure.mqtt.InboundDeployMessagePayload; import inc.sdt.blokworks.devicedeployer.infrastructure.mqtt.InboundDeployMessagePayload;
import inc.sdt.blokworks.devicedeployer.infrastructure.mqtt.OutboundMessagePayload; import inc.sdt.blokworks.devicedeployer.infrastructure.mqtt.OutboundMessagePayload;
import inc.sdt.blokworks.devicedeployer.presentation.exception.ConflictException;
import jakarta.servlet.http.HttpSession;
import org.eclipse.paho.client.mqttv3.IMqttClient; import org.eclipse.paho.client.mqttv3.IMqttClient;
import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.MqttMessage;
@ -22,6 +24,7 @@ public class DefaultDeployerService implements DeployerService{
private final IMqttClient mqttClient; private final IMqttClient mqttClient;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
private final DeployerRepositoryDelegate deployerRepositoryDelegate; private final DeployerRepositoryDelegate deployerRepositoryDelegate;
private String requestId;
public DefaultDeployerService(IMqttClient mqttClient, public DefaultDeployerService(IMqttClient mqttClient,
ObjectMapper objectMapper, ObjectMapper objectMapper,
@ -50,35 +53,31 @@ public class DefaultDeployerService implements DeployerService{
message.setPayload(bytes); message.setPayload(bytes);
mqttClient.publish("/assets/"+assetCode+"/apps/deploy", message); mqttClient.publish("/assets/"+assetCode+"/apps/deploy", message);
requestId = deployMessage.getRequestId();
log.info("[publish] message = {}", message);
}catch (JsonProcessingException | MqttException e) { }catch (JsonProcessingException | MqttException e) {
throw new IllegalArgumentException();
} }
} }
@Override @Override
public Mono<Void> apply(InboundDeployMessagePayload inboundDeployMessagePayload) { public Mono<Void> apply(InboundDeployMessagePayload payload) {
log.info("[apply] inboundDeployMessagePayload = {}", inboundDeployMessagePayload); log.info("[apply] inboundDeployMessagePayload = {}", payload);
// 배포된 앱 정보 저장 // 배포된 앱 정보 저장
// request Id 판별 // request Id 판별
if(requestId.equals(payload.requestId())) {
AssetApp assetApp = deployerRepositoryDelegate.save(fromMessage(inboundDeployMessagePayload)); return Mono.just(payload)
return null; .doOnNext(deployerRepositoryDelegate)
.then();
}else {
throw new ConflictException("This process is already exists.");
}
} }
@Override @Override
public Page<AssetApp> getAll(String assetCode, int page, int size) { public Page<AssetApp> getAll(String assetCode, int page, int size) {
log.debug("[getAll] assetCode = {}, page = {}, size = {}", assetCode, page, size); log.info("[getAll] assetCode = {}, page = {}, size = {}", assetCode, page, size);
return deployerRepositoryDelegate.findAllByAssetCode(assetCode, page, size); return deployerRepositoryDelegate.findAllByAssetCode(assetCode, page, size);
} }
private AssetApp fromMessage(InboundDeployMessagePayload payload) {
return AssetApp.builder()
.assetCode(payload.assetCode())
.name(payload.name())
.size(payload.size())
.releasedAt(payload.releasedAt())
.modifiedAt(payload.modifiedAt())
.build();
}
} }

View File

@ -1,14 +1,37 @@
package inc.sdt.blokworks.devicedeployer.application; package inc.sdt.blokworks.devicedeployer.application;
import inc.sdt.blokworks.devicedeployer.domain.Process;
import inc.sdt.blokworks.devicedeployer.infrastructure.mqtt.InboundProcessMessagePayload; import inc.sdt.blokworks.devicedeployer.infrastructure.mqtt.InboundProcessMessagePayload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
@Service @Service
public class DefaultProcessService implements ProcessService{ public class DefaultProcessService implements ProcessService{
private final Logger log;
private final ProcessRepositoryDelegate processRepositoryDelegate;
public DefaultProcessService(ProcessRepositoryDelegate processRepositoryDelegate) {
this.log = LoggerFactory.getLogger(this.getClass());
this.processRepositoryDelegate = processRepositoryDelegate;
}
@Override @Override
public Mono<Void> apply(InboundProcessMessagePayload inboundProcessMessagePayload) { public Mono<Void> apply(InboundProcessMessagePayload inboundProcessMessagePayload) {
return null; log.info("[apply] inboundProcessMessagePayload = {}", inboundProcessMessagePayload);
// 프로세스 저장
return Mono.just(inboundProcessMessagePayload)
.doOnNext(processRepositoryDelegate)
.then();
} }
@Override
public Page<Process> getAll(String assetCode, int page, int size) {
log.info("[getAll] assetCode = {}, page = {}, size = {}", assetCode, page, size);
return processRepositoryDelegate.findAllByAssetCode(assetCode, page, size);
}
} }

View File

@ -1,9 +1,12 @@
package inc.sdt.blokworks.devicedeployer.application; package inc.sdt.blokworks.devicedeployer.application;
import inc.sdt.blokworks.devicedeployer.domain.AssetApp; import inc.sdt.blokworks.devicedeployer.domain.AssetApp;
import inc.sdt.blokworks.devicedeployer.infrastructure.mqtt.InboundDeployMessagePayload;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
public interface DeployerRepositoryDelegate { import java.util.function.Consumer;
public interface DeployerRepositoryDelegate extends Consumer<InboundDeployMessagePayload> {
AssetApp save(AssetApp assetApp); AssetApp save(AssetApp assetApp);
Page<AssetApp> findAllByAssetCode(String assetCode, int page, int size); Page<AssetApp> findAllByAssetCode(String assetCode, int page, int size);
} }

View File

@ -0,0 +1,12 @@
package inc.sdt.blokworks.devicedeployer.application;
import inc.sdt.blokworks.devicedeployer.domain.Process;
import inc.sdt.blokworks.devicedeployer.infrastructure.mqtt.InboundProcessMessagePayload;
import org.springframework.data.domain.Page;
import java.util.function.Consumer;
public interface ProcessRepositoryDelegate extends Consumer<InboundProcessMessagePayload> {
Process save(Process process);
Page<Process> findAllByAssetCode(String assetCode, int page, int size);
}

View File

@ -1,10 +1,12 @@
package inc.sdt.blokworks.devicedeployer.application; package inc.sdt.blokworks.devicedeployer.application;
import inc.sdt.blokworks.devicedeployer.domain.Process;
import inc.sdt.blokworks.devicedeployer.infrastructure.mqtt.InboundProcessMessagePayload; import inc.sdt.blokworks.devicedeployer.infrastructure.mqtt.InboundProcessMessagePayload;
import org.springframework.data.domain.Page;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import java.util.function.Function; import java.util.function.Function;
public interface ProcessService extends Function<InboundProcessMessagePayload, Mono<Void>> { public interface ProcessService extends Function<InboundProcessMessagePayload, Mono<Void>> {
Page<Process> getAll(String assetCode, int page, int size);
} }

View File

@ -5,16 +5,16 @@ public class AssetApp {
private String name; private String name;
private Long size; private Long size;
private Long releaseAt; private Long releaseAt;
private Long modifiedAt; private Long updatedAt;
protected AssetApp() {} protected AssetApp() {}
public AssetApp(String assetCode, String name, Long size, Long releaseAt, Long modifiedAt) { public AssetApp(String assetCode, String name, Long size, Long releaseAt, Long updatedAt) {
this.assetCode = assetCode; this.assetCode = assetCode;
this.name = name; this.name = name;
this.size = size; this.size = size;
this.releaseAt = releaseAt; this.releaseAt = releaseAt;
this.modifiedAt = modifiedAt; this.updatedAt = updatedAt;
} }
public String getAssetCode() { public String getAssetCode() {
@ -33,8 +33,19 @@ public class AssetApp {
return releaseAt; return releaseAt;
} }
public Long getModifiedAt() { public Long getUpdatedAt() {
return modifiedAt; return updatedAt;
}
@Override
public String toString() {
return "AssetApp{" +
"assetCode='" + assetCode + '\'' +
", name='" + name + '\'' +
", size=" + size +
", releaseAt=" + releaseAt +
", updatedAt=" + updatedAt +
'}';
} }
public static Builder builder() { public static Builder builder() {
@ -46,7 +57,7 @@ public class AssetApp {
private String name; private String name;
private Long size; private Long size;
private Long releasedAt; private Long releasedAt;
private Long modifiedAt; private Long updatedAt;
public Builder assetCode(String assetCode) { public Builder assetCode(String assetCode) {
this.assetCode = assetCode; this.assetCode = assetCode;
@ -68,8 +79,8 @@ public class AssetApp {
return this; return this;
} }
public Builder modifiedAt(Long modifiedAt) { public Builder updatedAt(Long updatedAt) {
this.modifiedAt = modifiedAt; this.updatedAt = updatedAt;
return this; return this;
} }
@ -79,7 +90,7 @@ public class AssetApp {
assetApp.name = this.name; assetApp.name = this.name;
assetApp.size = this.size; assetApp.size = this.size;
assetApp.releaseAt = this.releasedAt; assetApp.releaseAt = this.releasedAt;
assetApp.modifiedAt = this.modifiedAt; assetApp.updatedAt = this.updatedAt;
return assetApp; return assetApp;
} }
} }

View File

@ -0,0 +1,139 @@
package inc.sdt.blokworks.devicedeployer.domain;
public class Process {
private String assetCode;
private Integer pid;
private String name;
private Integer cpu;
private Integer memory;
private Integer network;
private Long processedAt;
private OperationType operationType;
protected Process() {}
public Process(String assetCode, Integer pid, String name, Integer cpu, Integer memory, Integer network, Long processedAt, OperationType operationType) {
this.assetCode = assetCode;
this.pid = pid;
this.name = name;
this.cpu = cpu;
this.memory = memory;
this.network = network;
this.processedAt = processedAt;
this.operationType = operationType;
}
public String getAssetCode() {
return assetCode;
}
public Integer getPid() {
return pid;
}
public String getName() {
return name;
}
public Integer getCpu() {
return cpu;
}
public Integer getMemory() {
return memory;
}
public Integer getNetwork() {
return network;
}
public Long getProcessedAt() {
return processedAt;
}
public OperationType getOperationType() {
return operationType;
}
@Override
public String toString() {
return "Process{" +
"assetCode='" + assetCode + '\'' +
", pid=" + pid +
", name='" + name + '\'' +
", cpu=" + cpu +
", memory=" + memory +
", network=" + network +
", processedAt=" + processedAt +
", operationType=" + operationType +
'}';
}
public static Builder builder() {
return new Builder();
}
public static final class Builder {
private String assetCode;
private Integer pid;
private String name;
private Integer cpu;
private Integer memory;
private Integer network;
private Long processedAt;
private OperationType operationType;
public Builder assetCode(String assetCode) {
this.assetCode = assetCode;
return this;
}
public Builder pid(Integer pid) {
this.pid = pid;
return this;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder cpu(Integer cpu) {
this.cpu = cpu;
return this;
}
public Builder memory(Integer memory) {
this.memory = memory;
return this;
}
public Builder network(Integer network) {
this.network = network;
return this;
}
public Builder processedAt(Long processedAt) {
this.processedAt = processedAt;
return this;
}
public Builder operationType(OperationType operationType) {
this.operationType = operationType;
return this;
}
public Process build() {
Process process = new Process();
process.assetCode = this.assetCode;
process.pid = this.pid;
process.name = this.name;
process.cpu = this.cpu;
process.memory = this.memory;
process.network = this.network;
process.processedAt = this.processedAt;
process.operationType = this.operationType;
return process;
}
}
}

View File

@ -0,0 +1,73 @@
package inc.sdt.blokworks.devicedeployer.infrastructure.amqp;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "iam.amqp")
public class IamAmqpConnectionProperties {
private String host;
private Integer port;
private String username;
private String password;
private String exchange;
private String routingKey;
public String getHost() {
return host;
}
public Integer getPort() {
return port;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public String getExchange() {
return exchange;
}
public String getRoutingKey() {
return routingKey;
}
public void setHost(String host) {
this.host = host;
}
public void setPort(Integer port) {
this.port = port;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setExchange(String exchange) {
this.exchange = exchange;
}
public void setRoutingKey(String routingKey) {
this.routingKey = routingKey;
}
@Override
public String toString() {
return "IamAmqpConnectionProperties{" +
"host='" + host + '\'' +
", port=" + port +
", username='" + username + '\'' +
", password='" + password + '\'' +
", exchange='" + exchange + '\'' +
", routingKey='" + routingKey + '\'' +
'}';
}
}

View File

@ -0,0 +1,46 @@
package inc.sdt.blokworks.devicedeployer.infrastructure.amqp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.util.Map;
import java.util.Objects;
@ConditionalOnProperty(prefix = "iam", name = "enabled", havingValue = "true")
@Component
class RegisterResourceMappingDelegate {
private final String applicationName;
private final RegisterResourceProducer registerResourceProducer;
private final Logger log;
RegisterResourceMappingDelegate(@Value("${spring.application.name}") String applicationName,
RegisterResourceProducer registerResourceProducer) {
this.log = LoggerFactory.getLogger(RegisterResourceMappingDelegate.class);
log.info("[Constructor] applicationName={}", applicationName);
this.registerResourceProducer = registerResourceProducer;
this.applicationName = applicationName;
}
@EventListener
public void handleContextRefreshedEvent(ContextRefreshedEvent event) {
log.info("[handleContextRefreshedEvent] event = {}", event);
ApplicationContext applicationContext = event.getApplicationContext();
RequestMappingHandlerMapping requestMappingHandlerMapping = applicationContext.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> map = requestMappingHandlerMapping.getHandlerMethods();
map.values().stream()
.filter(value -> Objects.nonNull(value.getMethod().getDeclaredAnnotation(ResourceMapping.class)))
.map(value -> value.getMethod().getDeclaredAnnotation(ResourceMapping.class))
.map(annotation -> new ResourceMessagePayload(annotation.name(), annotation.method(), annotation.uri(), applicationName, annotation.description()))
.forEach(registerResourceProducer);
}
}

View File

@ -0,0 +1,56 @@
package inc.sdt.blokworks.devicedeployer.infrastructure.amqp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.function.Consumer;
@ConditionalOnProperty(prefix = "iam", name = "enabled", havingValue = "true")
@EnableConfigurationProperties({IamAmqpConnectionProperties.class})
@Component
class RegisterResourceProducer implements Consumer<ResourceMessagePayload> {
private final RabbitTemplate rabbitTemplate;
private final MessageConverter messageConverter;
private final MessageProperties messageProperties;
private final String exchange;
private final String routingKey;
private final Logger log;
RegisterResourceProducer(IamAmqpConnectionProperties connectionProperties) {
this.log = LoggerFactory.getLogger(this.getClass());
log.info("[Constructor] connectionProperties={}", connectionProperties);
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setHost(connectionProperties.getHost());
connectionFactory.setPort(connectionProperties.getPort());
connectionFactory.setUsername(connectionProperties.getUsername());
connectionFactory.setPassword(connectionProperties.getPassword());
this.messageConverter = new Jackson2JsonMessageConverter();
this.rabbitTemplate = new RabbitTemplate(connectionFactory);
this.rabbitTemplate.setMessageConverter(this.messageConverter);
this. exchange = connectionProperties.getExchange();
this.routingKey = connectionProperties.getRoutingKey();
this.messageProperties = new MessageProperties();
this.messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON);
this.messageProperties.setHeader("Command", "CREATE");
}
@Override
public void accept(ResourceMessagePayload payload) {
log.info("[accept] payload={}", payload);
Message message = messageConverter.toMessage(payload, messageProperties);
rabbitTemplate.send(exchange, routingKey, message);
}
}

View File

@ -0,0 +1,15 @@
package inc.sdt.blokworks.devicedeployer.infrastructure.amqp;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ResourceMapping {
String name();
String method();
String uri();
String description();
}

View File

@ -0,0 +1,10 @@
package inc.sdt.blokworks.devicedeployer.infrastructure.amqp;
record ResourceMessagePayload(
String name,
String method,
String uri,
String applicationName,
String description
) {
}

View File

@ -8,7 +8,7 @@ public record InboundDeployMessagePayload(
String name, String name,
Long size, Long size,
Long releasedAt, Long releasedAt,
Long modifiedAt, Long updatedAt,
String requestId String requestId
) { ) {
} }

View File

@ -18,17 +18,17 @@ class AssetAppEntity {
private Long size; private Long size;
@Column(name = "released_at") @Column(name = "released_at")
private Long releasedAt; private Long releasedAt;
@Column(name = "modfied_at") @Column(name = "updated_at")
private Long modifiedAt; private Long updatedAt;
protected AssetAppEntity() {} protected AssetAppEntity() {}
public AssetAppEntity(String assetCode, String name, long size, Long releasedAt, Long modifiedAt) { public AssetAppEntity(String assetCode, String name, long size, Long releasedAt, Long updatedAt) {
this.assetCode = assetCode; this.assetCode = assetCode;
this.name = name; this.name = name;
this.size = size; this.size = size;
this.releasedAt = releasedAt; this.releasedAt = releasedAt;
this.modifiedAt = modifiedAt; this.updatedAt = updatedAt;
} }
public String getId() { public String getId() {
@ -51,8 +51,8 @@ class AssetAppEntity {
return releasedAt; return releasedAt;
} }
public Long getModifiedAt() { public Long getUpdatedAt() {
return modifiedAt; return updatedAt;
} }

View File

@ -2,6 +2,7 @@ package inc.sdt.blokworks.devicedeployer.infrastructure.relational;
import inc.sdt.blokworks.devicedeployer.application.DeployerRepositoryDelegate; import inc.sdt.blokworks.devicedeployer.application.DeployerRepositoryDelegate;
import inc.sdt.blokworks.devicedeployer.domain.AssetApp; import inc.sdt.blokworks.devicedeployer.domain.AssetApp;
import inc.sdt.blokworks.devicedeployer.infrastructure.mqtt.InboundDeployMessagePayload;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
@ -20,9 +21,15 @@ public class AssetAppRelationalRepository implements DeployerRepositoryDelegate
this.assetAppJpaRepository = assetAppJpaRepository; this.assetAppJpaRepository = assetAppJpaRepository;
} }
@Override
public void accept(InboundDeployMessagePayload inboundDeployMessagePayload) {
this.save(this.fromMessage(inboundDeployMessagePayload));
}
// 앱 정보 저장 // 앱 정보 저장
@Override @Override
public AssetApp save(AssetApp assetApp) { public AssetApp save(AssetApp assetApp) {
log.info("[save] assetApp = {}", assetApp);
AssetAppEntity entity = this.toEntity(assetApp); AssetAppEntity entity = this.toEntity(assetApp);
assetAppJpaRepository.save(entity); assetAppJpaRepository.save(entity);
return assetApp; return assetApp;
@ -30,7 +37,7 @@ public class AssetAppRelationalRepository implements DeployerRepositoryDelegate
@Override @Override
public Page<AssetApp> findAllByAssetCode(String assetCode, int page, int size) { public Page<AssetApp> findAllByAssetCode(String assetCode, int page, int size) {
log.debug("[findAllByAssetCode] assetCode = {}", assetCode); log.info("[findAllByAssetCode] assetCode = {}", assetCode);
Pageable pageable = page < 0 ? Pageable.unpaged() : PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "releasedAt")); Pageable pageable = page < 0 ? Pageable.unpaged() : PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "releasedAt"));
return assetAppJpaRepository.findAllByAssetCode(assetCode, pageable) return assetAppJpaRepository.findAllByAssetCode(assetCode, pageable)
.map(this::fromEntity); .map(this::fromEntity);
@ -42,7 +49,7 @@ public class AssetAppRelationalRepository implements DeployerRepositoryDelegate
assetApp.getName(), assetApp.getName(),
assetApp.getSize(), assetApp.getSize(),
assetApp.getReleaseAt(), assetApp.getReleaseAt(),
assetApp.getModifiedAt() assetApp.getUpdatedAt()
); );
} }
@ -52,8 +59,17 @@ public class AssetAppRelationalRepository implements DeployerRepositoryDelegate
.name(entity.getName()) .name(entity.getName())
.size(entity.getSize()) .size(entity.getSize())
.releasedAt(entity.getReleasedAt()) .releasedAt(entity.getReleasedAt())
.modifiedAt(entity.getModifiedAt()) .updatedAt(entity.getUpdatedAt())
.build(); .build();
} }
private AssetApp fromMessage(InboundDeployMessagePayload payload) {
return AssetApp.builder()
.assetCode(payload.assetCode())
.name(payload.name())
.size(payload.size())
.releasedAt(payload.releasedAt())
.updatedAt(payload.updatedAt())
.build();
}
} }

View File

@ -3,44 +3,42 @@ package inc.sdt.blokworks.devicedeployer.infrastructure.relational;
import jakarta.persistence.*; import jakarta.persistence.*;
@Entity(name = "app_process") @Entity(name = "app_process")
class AppProcessEntity { class ProcessEntity {
@Id @Id
@GeneratedValue(strategy = GenerationType.UUID) @GeneratedValue(strategy = GenerationType.UUID)
@Column(name = "id") @Column(name = "process_id", length = 36)
private String id; private String id;
@Column(name = "app_id", length = 36)
private String appId;
@Column(name = "asset_code", length = 255) @Column(name = "asset_code", length = 255)
private String assetCode; private String assetCode;
@Column(name = "pid") @Column(name = "pid")
private Integer pid; private Integer pid;
@Column(name = "app_name", length = 255)
private String appName;
@Column(name = "cpu") @Column(name = "cpu")
private Integer cpu; private Integer cpu;
@Column(name = "memory") @Column(name = "memory")
private Integer memory; private Integer memory;
@Column(name = "network") @Column(name = "network")
private Integer network; private Integer network;
@Column(name = "processed_at")
private Long processedAt;
protected AppProcessEntity() {} protected ProcessEntity() {}
public AppProcessEntity(String id, String appId, String assetCode, Integer pid, Integer cpu, Integer memory, Integer network) { public ProcessEntity(String assetCode, Integer pid, String appName, Integer cpu, Integer memory, Integer network, Long processedAt) {
this.id = id;
this.appId = appId;
this.assetCode = assetCode; this.assetCode = assetCode;
this.pid = pid; this.pid = pid;
this.appName = appName;
this.cpu = cpu; this.cpu = cpu;
this.memory = memory; this.memory = memory;
this.network = network; this.network = network;
this.processedAt = processedAt;
} }
public String getId() { public String getId() {
return id; return id;
} }
public String getAppId() {
return appId;
}
public String getAssetCode() { public String getAssetCode() {
return assetCode; return assetCode;
} }
@ -49,6 +47,10 @@ class AppProcessEntity {
return pid; return pid;
} }
public String getAppName() {
return appName;
}
public Integer getCpu() { public Integer getCpu() {
return cpu; return cpu;
} }
@ -60,4 +62,8 @@ class AppProcessEntity {
public Integer getNetwork() { public Integer getNetwork() {
return network; return network;
} }
public Long getProcessedAt() {
return processedAt;
}
} }

View File

@ -0,0 +1,9 @@
package inc.sdt.blokworks.devicedeployer.infrastructure.relational;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProcessJpaRepository extends JpaRepository<ProcessEntity, String> {
Page<ProcessEntity> findAllByAssetCode(String assetCode, Pageable pageable);
}

View File

@ -0,0 +1,80 @@
package inc.sdt.blokworks.devicedeployer.infrastructure.relational;
import inc.sdt.blokworks.devicedeployer.application.ProcessRepositoryDelegate;
import inc.sdt.blokworks.devicedeployer.domain.Process;
import inc.sdt.blokworks.devicedeployer.infrastructure.mqtt.InboundProcessMessagePayload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
@Component
public class ProcessRelationalRepository implements ProcessRepositoryDelegate {
private final Logger log;
private final ProcessJpaRepository processJpaRepository;
public ProcessRelationalRepository(ProcessJpaRepository processJpaRepository) {
this.log = LoggerFactory.getLogger(this.getClass());
this.processJpaRepository = processJpaRepository;
}
@Override
public void accept(InboundProcessMessagePayload payload) {
this.save(this.fromMessage(payload));
}
@Override
public Process save(Process process) {
log.info("[save] process = {}", process);
ProcessEntity entity = this.toEntity(process);
processJpaRepository.save(entity);
return process;
}
@Override
public Page<Process> findAllByAssetCode(String assetCode, int page, int size) {
log.info("[findAllByAssetCode] assetCode = {}", assetCode);
Pageable pageable = page < 0 ? Pageable.unpaged() : PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "processedAt"));
return processJpaRepository.findAllByAssetCode(assetCode, pageable)
.map(this::fromEntity);
}
private ProcessEntity toEntity(Process process) {
return new ProcessEntity(
process.getAssetCode(),
process.getPid(),
process.getName(),
process.getCpu(),
process.getMemory(),
process.getNetwork(),
process.getProcessedAt()
);
}
private Process fromEntity(ProcessEntity entity) {
return Process.builder()
.assetCode(entity.getAssetCode())
.pid(entity.getPid())
.name(entity.getAppName())
.cpu(entity.getCpu())
.memory(entity.getMemory())
.network(entity.getNetwork())
.processedAt(entity.getProcessedAt())
.build();
}
private Process fromMessage(InboundProcessMessagePayload payload) {
return Process.builder()
.assetCode(payload.assetCode())
.pid(payload.pid())
.name(payload.name())
.cpu(payload.cpu())
.memory(payload.memory())
.network(payload.network())
.processedAt(payload.processedAt())
.build();
}
}

View File

@ -5,6 +5,6 @@ record AssetAppResource(
String name, String name,
Long size, Long size,
Long releaseAt, Long releaseAt,
Long modifiedAt Long updatedAt
) { ) {
} }

View File

@ -12,7 +12,7 @@ public class AssetAppResourceConverter {
.name(resource.name()) .name(resource.name())
.size(resource.size()) .size(resource.size())
.releasedAt(resource.releaseAt()) .releasedAt(resource.releaseAt())
.modifiedAt(resource.modifiedAt()) .updatedAt(resource.updatedAt())
.build(); .build();
} }
@ -22,7 +22,7 @@ public class AssetAppResourceConverter {
assetApp.getName(), assetApp.getName(),
assetApp.getSize(), assetApp.getSize(),
assetApp.getReleaseAt(), assetApp.getReleaseAt(),
assetApp.getModifiedAt() assetApp.getUpdatedAt()
); );
} }
} }

View File

@ -1,9 +1,11 @@
package inc.sdt.blokworks.devicedeployer.presentation; package inc.sdt.blokworks.devicedeployer.presentation;
import inc.sdt.blokworks.devicedeployer.application.DeployerService; import inc.sdt.blokworks.devicedeployer.application.DeployerService;
import inc.sdt.blokworks.devicedeployer.application.ProcessService;
import inc.sdt.blokworks.devicedeployer.domain.AssetApp; import inc.sdt.blokworks.devicedeployer.domain.AssetApp;
import inc.sdt.blokworks.devicedeployer.domain.OperationType; import inc.sdt.blokworks.devicedeployer.domain.OperationType;
import inc.sdt.blokworks.devicedeployer.domain.OutboundMessage; import inc.sdt.blokworks.devicedeployer.domain.OutboundMessage;
import inc.sdt.blokworks.devicedeployer.domain.Process;
import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -40,9 +42,10 @@ public class DeployerController {
public void deploy(@PathVariable String assetCode, public void deploy(@PathVariable String assetCode,
@Valid @RequestBody OutboundMessageResource assetAppResource) { @Valid @RequestBody OutboundMessageResource assetAppResource) {
log.info("[deploy] assetCode = {}, assetAppResource = {}", assetCode, assetAppResource); log.info("[deploy] assetCode = {}, assetAppResource = {}", assetCode, assetAppResource);
String requestId = UUID.randomUUID().toString();
OutboundMessage outboundMessage = outboundMessageResourceConverter.fromResource(assetAppResource); OutboundMessage outboundMessage = outboundMessageResourceConverter.fromResource(assetAppResource);
outboundMessage.setRequestId("requestId"); outboundMessage.setRequestId(requestId);
deployerService.publish(outboundMessageResourceConverter.fromResource(assetAppResource), assetCode); deployerService.publish(outboundMessage, assetCode);
} }
/** /**
@ -52,8 +55,8 @@ public class DeployerController {
@ResponseStatus(HttpStatus.OK) @ResponseStatus(HttpStatus.OK)
@GetMapping("/assets/{assetCode}/apps") @GetMapping("/assets/{assetCode}/apps")
public PageableResponse<AssetAppResource> get(@PathVariable String assetCode, public PageableResponse<AssetAppResource> get(@PathVariable String assetCode,
@RequestParam(required = false, defaultValue = "0") int page, @RequestParam(required = false, defaultValue = "0") int page,
@RequestParam(required = false, defaultValue = "20") int size) { @RequestParam(required = false, defaultValue = "20") int size) {
log.info("[get] assetCode = {}, page = {}, size = {}", assetCode, page, size); log.info("[get] assetCode = {}, page = {}, size = {}", assetCode, page, size);
Page<AssetApp> assetApps = deployerService.getAll(assetCode, page, size); Page<AssetApp> assetApps = deployerService.getAll(assetCode, page, size);
return PageableResponse.from(assetApps, assetAppResourceConverter::toResource); return PageableResponse.from(assetApps, assetAppResourceConverter::toResource);

View File

@ -1,6 +1,5 @@
package inc.sdt.blokworks.devicedeployer.presentation; package inc.sdt.blokworks.devicedeployer.presentation;
import com.fasterxml.jackson.databind.ObjectMapper;
import inc.sdt.blokworks.devicedeployer.application.DeployerService; import inc.sdt.blokworks.devicedeployer.application.DeployerService;
import inc.sdt.blokworks.devicedeployer.application.ProcessService; import inc.sdt.blokworks.devicedeployer.application.ProcessService;
import inc.sdt.blokworks.devicedeployer.infrastructure.mqtt.InboundDeployMessagePayload; import inc.sdt.blokworks.devicedeployer.infrastructure.mqtt.InboundDeployMessagePayload;
@ -17,30 +16,27 @@ public class MqttMessageHandler {
private final Logger log; private final Logger log;
private final DeployerService deployerService; private final DeployerService deployerService;
private final ProcessService processService; private final ProcessService processService;
private final ObjectMapper objectMapper;
private final MqttMessageConverter<InboundDeployMessagePayload> deployMessagePayloadConverter; private final MqttMessageConverter<InboundDeployMessagePayload> deployMessagePayloadConverter;
private final MqttMessageConverter<InboundProcessMessagePayload> processMessagePayloadConverter; private final MqttMessageConverter<InboundProcessMessagePayload> processMessagePayloadConverter;
public MqttMessageHandler(DeployerService deployerService, public MqttMessageHandler(DeployerService deployerService,
ProcessService processService, ProcessService processService,
ObjectMapper objectMapper,
MqttMessageConverter<InboundDeployMessagePayload> deployMessagePayloadConverter, MqttMessageConverter<InboundDeployMessagePayload> deployMessagePayloadConverter,
MqttMessageConverter<InboundProcessMessagePayload> processMessagePayloadConverter) { MqttMessageConverter<InboundProcessMessagePayload> processMessagePayloadConverter) {
this.log = LoggerFactory.getLogger(this.getClass()); this.log = LoggerFactory.getLogger(this.getClass());
this.deployerService = deployerService; this.deployerService = deployerService;
this.processService = processService; this.processService = processService;
this.objectMapper = objectMapper;
this.deployMessagePayloadConverter = deployMessagePayloadConverter; this.deployMessagePayloadConverter = deployMessagePayloadConverter;
this.processMessagePayloadConverter = processMessagePayloadConverter; this.processMessagePayloadConverter = processMessagePayloadConverter;
} }
@ServiceActivator(inputChannel = "mqttInboundChannel") @ServiceActivator(inputChannel = "mqttInboundChannel")
void handleMessage(Message<String> message) { void handleMessage(Message<String> message) {
log.debug("[handleMessage] message={}", message); log.info("[handleMessage] message={}", message);
if(!message.getPayload().contains("pid")) { if(!message.getPayload().contains("pid")) {
String topic = String.valueOf(message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC)); String topic = String.valueOf(message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC));
String id = topic.split("/")[1]; String id = topic.split("/")[4];
log.info("[handleMessage] topic = {}, id = {}", topic, id); log.info("[handleMessage] topic = {}, id = {}", topic, id);
@ -51,7 +47,7 @@ public class MqttMessageHandler {
p.name(), p.name(),
p.size(), p.size(),
p.releasedAt(), p.releasedAt(),
p.modifiedAt(), p.updatedAt(),
id)) id))
.flatMap(deployerService) .flatMap(deployerService)
.subscribe(); .subscribe();

View File

@ -0,0 +1,39 @@
package inc.sdt.blokworks.devicedeployer.presentation;
import inc.sdt.blokworks.devicedeployer.application.ProcessService;
import inc.sdt.blokworks.devicedeployer.domain.Process;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@RestController
public class ProcessController {
private final Logger log;
private final ProcessService processService;
private final ProcessResourceConverter processResourceConverter;
public ProcessController(ProcessService processService,
ProcessResourceConverter processResourceConverter) {
this.log = LoggerFactory.getLogger(this.getClass());
this.processService = processService;
this.processResourceConverter = processResourceConverter;
}
/**
* ( )
* @param assetCode
*/
@ResponseStatus(HttpStatus.OK)
@GetMapping("/assets/{assetCode}/apps/process")
public PageableResponse<ProcessResource> get(@PathVariable String assetCode,
@RequestParam(required = false, defaultValue = "0") int page,
@RequestParam(required = false, defaultValue = "20") int size) {
log.info("[get] assetCode = {}, page = {}, size = {}", assetCode, page, size);
Page<Process> process = processService.getAll(assetCode, page, size);
return PageableResponse.from(process, processResourceConverter::toResource);
}
}

View File

@ -0,0 +1,15 @@
package inc.sdt.blokworks.devicedeployer.presentation;
import inc.sdt.blokworks.devicedeployer.domain.OperationType;
record ProcessResource(
String assetCode,
Integer pid,
String name,
Integer cpu,
Integer memory,
Integer network,
Long processedAt,
OperationType operationType
) {
}

View File

@ -0,0 +1,33 @@
package inc.sdt.blokworks.devicedeployer.presentation;
import inc.sdt.blokworks.devicedeployer.domain.Process;
import org.springframework.stereotype.Component;
@Component
public class ProcessResourceConverter {
public Process fromResource(ProcessResource resource) {
return Process.builder()
.assetCode(resource.assetCode())
.name(resource.name())
.pid(resource.pid())
.cpu(resource.cpu())
.memory(resource.memory())
.network(resource.network())
.processedAt(resource.processedAt())
.operationType(resource.operationType())
.build();
}
public ProcessResource toResource(Process process) {
return new ProcessResource(
process.getAssetCode(),
process.getPid(),
process.getName(),
process.getCpu(),
process.getMemory(),
process.getNetwork(),
process.getProcessedAt(),
process.getOperationType()
);
}
}

View File

@ -12,7 +12,7 @@ import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageChannel;
@Configuration @Configuration
@EnableConfigurationProperties(MqttConfigurationProperties.class) @EnableConfigurationProperties({MqttConfigurationProperties.class})
public class MqttConfiguration { public class MqttConfiguration {
@Bean(name = "mqttInboundChannel") @Bean(name = "mqttInboundChannel")
MessageChannel mqttMessageChannel() { MessageChannel mqttMessageChannel() {
@ -20,7 +20,7 @@ public class MqttConfiguration {
} }
@Bean @Bean
MessageProducer messageChannel(MessageChannel mqttMessageChannel, MessageProducer inboundChannel(MessageChannel mqttInboundChannel,
MqttConfigurationProperties mqttConfigurationProperties) { MqttConfigurationProperties mqttConfigurationProperties) {
MqttConnectOptions options = new MqttConnectOptions(); MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(mqttConfigurationProperties.getUsername()); options.setUserName(mqttConfigurationProperties.getUsername());
@ -39,7 +39,7 @@ public class MqttConfiguration {
adapter.setConverter(new DefaultPahoMessageConverter()); adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(1); adapter.setQos(1);
adapter.addTopic(mqttConfigurationProperties.getTopics()); adapter.addTopic(mqttConfigurationProperties.getTopics());
adapter.setOutputChannel(mqttMessageChannel); adapter.setOutputChannel(mqttInboundChannel);
return adapter; return adapter;
} }

View File

@ -0,0 +1,13 @@
package inc.sdt.blokworks.devicedeployer.presentation.exception;
public class ConflictException extends RuntimeException{
private static final String DEFAULT_MESSAGE = "Already exists";
public ConflictException() {
super(DEFAULT_MESSAGE);
}
public ConflictException(String message) {
super(message);
}
}

View File

@ -0,0 +1,35 @@
package inc.sdt.blokworks.devicedeployer.presentation.exception;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
class ControllerAdvice {
private final Logger log;
ControllerAdvice() {
this.log = LoggerFactory.getLogger(ControllerAdvice.class);
}
@ExceptionHandler(NoContentException.class)
@ResponseStatus(HttpStatus.NO_CONTENT)
public void handleNoContentException(Exception exception) {
}
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleIllegalArgumentException(IllegalArgumentException exception) {
return new ErrorResponse(HttpStatus.BAD_REQUEST, exception.getMessage());
}
@ExceptionHandler(ConflictException.class)
@ResponseStatus(HttpStatus.CONFLICT)
public ErrorResponse handleConflictException(Exception exception) {
return new ErrorResponse(HttpStatus.CONFLICT, exception.getMessage());
}
}

View File

@ -0,0 +1,37 @@
package inc.sdt.blokworks.devicedeployer.presentation.exception;
import org.springframework.http.HttpStatus;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneOffset;
class ErrorResponse {
private final int code;
private final String error;
private final String message;
private final Long timestamp;
public ErrorResponse(HttpStatus code, String message) {
this.code = code.value();
this.error = code.getReasonPhrase();
this.message = message;
this.timestamp = Instant.now(Clock.system(ZoneOffset.UTC)).toEpochMilli();
}
public int getCode() {
return code;
}
public String getError() {
return error;
}
public String getMessage() {
return message;
}
public Long getTimestamp() {
return timestamp;
}
}

View File

@ -0,0 +1,13 @@
package inc.sdt.blokworks.devicedeployer.presentation.exception;
public class NoContentException extends RuntimeException{
private static final String DEFAULT_MESSAGE = "No Content";
public NoContentException() {
super(DEFAULT_MESSAGE);
}
public NoContentException(String message) {
super(message);
}
}

View File

@ -0,0 +1,9 @@
iam:
enabled: ${IAM_REGISTER_ENABLED}
amqp:
host: ${IAM_AMQP_HOST}
port: ${IAM_AMQP_PORT}
username: ${IAM_AMQP_CREDENTIALS_USERNAME}
password: ${IAM_AMQP_CREDENTIALS_PASSWORD}
exchange: ${IAM_AMQP_EXCHANGE}
routing-key: ${IAM_AMQP_ROUTING_KEY}