From e4f24bf3a6bea922897bda7ee2066c339e5aaeaa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E1=84=8C=E1=85=A1=E1=86=BC=E1=84=89=E1=85=A5=E1=86=AB?=
 =?UTF-8?q?=E1=84=8B=E1=85=A2?= <sa.jang@sdt.inc>
Date: Fri, 8 Sep 2023 10:43:51 +0900
Subject: [PATCH] =?UTF-8?q?1.=20Tube=20On/Off=20=EC=A0=9C=EC=96=B4=20?=
 =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=202.=20k8s=20=EB=B0=B0?=
 =?UTF-8?q?=ED=8F=AC=20=EA=B4=80=EB=A0=A8=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94?=
 =?UTF-8?q?=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 k8s/command/docker-build-push.sh              |  4 ++
 k8s/control-center-management-k8s.yml         |  3 ++
 k8s/deploy.yaml                               | 42 +++++++++++++++++++
 k8s/svc.yaml                                  | 15 +++++++
 rest/api.http                                 | 16 +++++++
 .../application/DefaultTubeService.java       | 21 ++++------
 .../application/TubeService.java              |  3 +-
 .../presentation/TubeController.java          |  7 +++-
 .../exception/ExceptionResponse.java          | 25 +++++++++++
 .../RestControllerExceptionHandler.java       | 33 +++++++++++++++
 10 files changed, 153 insertions(+), 16 deletions(-)
 create mode 100644 k8s/command/docker-build-push.sh
 create mode 100644 k8s/control-center-management-k8s.yml
 create mode 100644 k8s/deploy.yaml
 create mode 100644 k8s/svc.yaml
 create mode 100644 rest/api.http
 create mode 100644 src/main/java/inc/sdt/controlcentermanagement/presentation/exception/ExceptionResponse.java
 create mode 100644 src/main/java/inc/sdt/controlcentermanagement/presentation/exception/RestControllerExceptionHandler.java

diff --git a/k8s/command/docker-build-push.sh b/k8s/command/docker-build-push.sh
new file mode 100644
index 0000000..a583e26
--- /dev/null
+++ b/k8s/command/docker-build-push.sh
@@ -0,0 +1,4 @@
+aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin 003960268191.dkr.ecr.ap-northeast-2.amazonaws.com
+docker build --platform linux/amd64 -t control-center-management ../../
+docker tag control-center-management:latest 003960268191.dkr.ecr.ap-northeast-2.amazonaws.com/sdt-cloud/control-center-management:0.0.1
+docker push 003960268191.dkr.ecr.ap-northeast-2.amazonaws.com/sdt-cloud/control-center-management:0.0.1
\ No newline at end of file
diff --git a/k8s/control-center-management-k8s.yml b/k8s/control-center-management-k8s.yml
new file mode 100644
index 0000000..0a3451d
--- /dev/null
+++ b/k8s/control-center-management-k8s.yml
@@ -0,0 +1,3 @@
+SERVER_PORT: 8087
+MONGODB_URL: mongodb://sdt:251327@13.209.39.139:27017/awexomeray?authSource=admin
+DEPLOYER_ENDPOINT: http://device-deployer.sdt-cloud.svc.cluster.local:8085
diff --git a/k8s/deploy.yaml b/k8s/deploy.yaml
new file mode 100644
index 0000000..12c8123
--- /dev/null
+++ b/k8s/deploy.yaml
@@ -0,0 +1,42 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: control-center-management
+  namespace: sdt-cloud
+  labels:
+    name: control-center-management
+spec:
+  selector:
+    matchLabels:
+      name: control-center-management
+  replicas: 1
+  template:
+    metadata:
+      labels:
+        name: control-center-management
+    spec:
+      imagePullSecrets:
+      - name: regcred
+      containers:
+        - name: control-center-management
+          image: 003960268191.dkr.ecr.ap-northeast-2.amazonaws.com/sdt-cloud/control-center-management:0.0.1
+          imagePullPolicy: Always
+          ports:
+            - containerPort: 8087
+          env:
+            - name: SPRING_PROFILES_ACTIVE
+              value: "k8s"
+            - name: SERVER_PORT
+              value: "8087"
+            - name: MONGODB_URL
+              value: "mongodb://sdt:251327@mongo-db.database.svc.cluster.local/awexomeray?authSource=admin"
+            - name: DEPLOYER_ENDPOINT
+              value: "http://device-deployer.sdt-cloud.svc.cluster.local:8085"
+            - name: IAM_AMQP_HOST
+              value: "rabbitmq.sdt-cloud.svc.cluster.local"
+            - name: IAM_AMQP_PORT
+              value: "5672"
+            - name: IAM_AMQP_CREDENTIALS_USERNAME
+              value: "sdt"
+            - name: IAM_AMQP_CREDENTIALS_PASSWORD
+              value: "251327"
diff --git a/k8s/svc.yaml b/k8s/svc.yaml
new file mode 100644
index 0000000..da473be
--- /dev/null
+++ b/k8s/svc.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: control-center-management
+  namespace: sdt-cloud
+spec:
+  ports:
+    - port: 8087
+      targetPort: 8087
+      protocol: TCP
+      name: control-center-management
+      nodePort: 30870
+  type: NodePort
+  selector:
+    name: control-center-management
\ No newline at end of file
diff --git a/rest/api.http b/rest/api.http
new file mode 100644
index 0000000..ef26b28
--- /dev/null
+++ b/rest/api.http
@@ -0,0 +1,16 @@
+### ON/OFF 제어
+#PATCH http://13.209.39.139:30870/chamber/1/tube/toggle
+PATCH http://localhost:8087/chamber/1/tube/toggle
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJvcmdhbml6YXRpb25JZCI6ImQxZGJlYWExLWY5ZTUtNGE4OC1hMWM4LTYzMWQ4NTMyOWJmYyIsInJvbGVzIjpbIlJPTEVfQURNSU5JU1RSQVRPUiJdLCJpZCI6IjcyYTUxZDUwLTYwMDEtNDAwYy04NjNmLWZlOGU2OGE1MGExMCIsInN1YiI6InNhLmphbmdAc2R0LmluYyIsImlhdCI6MTY5NDEzNjg4OCwiZXhwIjoxNjk0MTQwNDg4fQ.likh7Ix7p4Yf2iwankLG71VGrgCDzQzhn9Z7QJK0blg
+
+{
+  "command" : "OFF",
+  "assetCode" : "NODEQ-TEST-1",
+  "slotNumber" : "1-1",
+  "tube": [
+    {"code": "1"}, {"code": "3"}, {"code": "11"}, {"code": "16"}
+  ]
+}
+
+//{"command": "ON"}
\ No newline at end of file
diff --git a/src/main/java/inc/sdt/controlcentermanagement/application/DefaultTubeService.java b/src/main/java/inc/sdt/controlcentermanagement/application/DefaultTubeService.java
index 369db73..a5c0433 100644
--- a/src/main/java/inc/sdt/controlcentermanagement/application/DefaultTubeService.java
+++ b/src/main/java/inc/sdt/controlcentermanagement/application/DefaultTubeService.java
@@ -1,10 +1,7 @@
 package inc.sdt.controlcentermanagement.application;
 
 import com.fasterxml.jackson.core.type.TypeReference;
-import inc.sdt.controlcentermanagement.domain.CommandType;
-import inc.sdt.controlcentermanagement.domain.DeployRequest;
-import inc.sdt.controlcentermanagement.domain.Slot;
-import inc.sdt.controlcentermanagement.domain.Tube;
+import inc.sdt.controlcentermanagement.domain.*;
 import inc.sdt.controlcentermanagement.infrastructure.resttemplate.RestTemplateRequestHandler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -45,7 +42,7 @@ class DefaultTubeService implements TubeService {
         log.info("[controlByTubeCode] request: {}", request);
         Map<String, String> tubeMap = new LinkedHashMap<>();
         for (Tube tube : request.getTube()) {
-            Map<String, Integer> tubeOffsetMap = this.tubeOffset(request);
+            Map<String, Integer> tubeOffsetMap = this.tubeOffset();
             String offsetKey = request.getSlotNumber().substring(request.getSlotNumber().length() - 1);
             int tubeOffset = tubeOffsetMap.getOrDefault(offsetKey, 1);
             int tubeKey = Integer.parseInt(tube.getCode()) + tubeOffset;
@@ -64,10 +61,9 @@ class DefaultTubeService implements TubeService {
     }
 
     @Override
-    public void controlAll(String authorization, Slot request) {
-        log.info("[controlAll] request: {}", request);
+    public void controlAll(String authorization, List<Slot> slots, Command command) {
+        log.info("[controlAll] chamberNumber: {}, command: {}", slots.get(0).getChamberNumber(), command);
 
-        List<Slot> slots = this.get(request.getChamberNumber());
         Map<String, Map<String, Map<String, String>>> resultMap = new LinkedHashMap<>();
         for (Slot slot : slots) {
             String assetCode = slot.getAssetCode();
@@ -78,12 +74,11 @@ class DefaultTubeService implements TubeService {
             Map<String, String> slotMap = assetMap.computeIfAbsent(slotNumber, k -> new HashMap<>());
 
             for (Tube tube : tubes) {
-                Map<String, Integer> tubeOffsetMap = this.tubeOffset(request);
+                Map<String, Integer> tubeOffsetMap = this.tubeOffset();
                 String offsetKey = slotNumber.substring(slotNumber.length() - 1);
                 int tubeOffset = tubeOffsetMap.getOrDefault(offsetKey, 1);
                 int offsetResult = Integer.parseInt(tube.getCode()) + tubeOffset;
-
-                slotMap.put(String.valueOf(offsetResult), String.valueOf(request.getCommand().ordinal()));
+                slotMap.put(String.valueOf(offsetResult), String.valueOf(command.ordinal()));
             }
         }
 
@@ -100,9 +95,7 @@ class DefaultTubeService implements TubeService {
         }));
     }
 
-    private Map<String, Integer> tubeOffset(Slot request) {
-        log.info("[tubeOffset] request: {}", request);
-
+    private Map<String, Integer> tubeOffset() {
         int offset = 0;
         Map<String, Integer> tubeOffsetMap = new HashMap<>();
         for (int i = 1; i <= 8; i++) {
diff --git a/src/main/java/inc/sdt/controlcentermanagement/application/TubeService.java b/src/main/java/inc/sdt/controlcentermanagement/application/TubeService.java
index 03ab0e5..a05a0f9 100644
--- a/src/main/java/inc/sdt/controlcentermanagement/application/TubeService.java
+++ b/src/main/java/inc/sdt/controlcentermanagement/application/TubeService.java
@@ -1,5 +1,6 @@
 package inc.sdt.controlcentermanagement.application;
 
+import inc.sdt.controlcentermanagement.domain.Command;
 import inc.sdt.controlcentermanagement.domain.Slot;
 
 import java.util.List;
@@ -10,5 +11,5 @@ import java.util.List;
 public interface TubeService {
     List<Slot> get(String chamberNumber);
     void controlByTubeCode(String authorization, Slot request);
-    void controlAll(String authorization, Slot request);
+    void controlAll(String authorization, List<Slot> slots, Command command);
 }
diff --git a/src/main/java/inc/sdt/controlcentermanagement/presentation/TubeController.java b/src/main/java/inc/sdt/controlcentermanagement/presentation/TubeController.java
index 8ddbd9f..a58f5fe 100644
--- a/src/main/java/inc/sdt/controlcentermanagement/presentation/TubeController.java
+++ b/src/main/java/inc/sdt/controlcentermanagement/presentation/TubeController.java
@@ -8,6 +8,9 @@ import org.slf4j.LoggerFactory;
 import org.springframework.http.HttpStatus;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.List;
+import java.util.NoSuchElementException;
+
 /**
  * @author sunae.jang (sa.jang@sdt.inc)
  */
@@ -26,11 +29,13 @@ public class TubeController {
     @PatchMapping("/chamber/{chamberNumber}/tube/toggle")
     public void toggle(@RequestHeader("Authorization") String authorization, @PathVariable String chamberNumber, @RequestBody Slot slot) {
         log.info("[toggle] chamberNumber: {}, slot: {}", chamberNumber, slot);
+        List<Slot> slots = tubeService.get(chamberNumber);
+        if (slots.isEmpty()) throw new NoSuchElementException(chamberNumber);
         slot.setChamberNumber(chamberNumber);
         if (slot.getTube() != null) {
             tubeService.controlByTubeCode(authorization, slot);
         } else {
-            tubeService.controlAll(authorization, slot);
+            tubeService.controlAll(authorization, slots, slot.getCommand());
         }
     }
 }
diff --git a/src/main/java/inc/sdt/controlcentermanagement/presentation/exception/ExceptionResponse.java b/src/main/java/inc/sdt/controlcentermanagement/presentation/exception/ExceptionResponse.java
new file mode 100644
index 0000000..423726c
--- /dev/null
+++ b/src/main/java/inc/sdt/controlcentermanagement/presentation/exception/ExceptionResponse.java
@@ -0,0 +1,25 @@
+package inc.sdt.controlcentermanagement.presentation.exception;
+
+import org.springframework.http.HttpStatus;
+
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+
+record ExceptionResponse(int code, String error, String message, long timestamp) {
+    public static Builder builder(HttpStatus httpStatus) {
+        return new Builder(httpStatus);
+    }
+
+    public static class Builder {
+        private HttpStatus httpStatus;
+
+        private Builder(HttpStatus httpStatus) {
+            this.httpStatus = httpStatus;
+        }
+
+        public ExceptionResponse build(String message) {
+            final long timeStamp = LocalDateTime.now().toInstant(ZoneOffset.UTC).toEpochMilli();
+            return new ExceptionResponse(this.httpStatus.value(), this.httpStatus.getReasonPhrase(), message, timeStamp);
+        }
+    }
+}
diff --git a/src/main/java/inc/sdt/controlcentermanagement/presentation/exception/RestControllerExceptionHandler.java b/src/main/java/inc/sdt/controlcentermanagement/presentation/exception/RestControllerExceptionHandler.java
new file mode 100644
index 0000000..46b58ec
--- /dev/null
+++ b/src/main/java/inc/sdt/controlcentermanagement/presentation/exception/RestControllerExceptionHandler.java
@@ -0,0 +1,33 @@
+package inc.sdt.controlcentermanagement.presentation.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import java.util.NoSuchElementException;
+
+@RestControllerAdvice
+class RestControllerExceptionHandler {
+
+    @ExceptionHandler
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    ExceptionResponse handleIllegalArgumentException(IllegalArgumentException e) {
+        return ExceptionResponse.builder(HttpStatus.BAD_REQUEST)
+                .build(e.getMessage());
+    }
+
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    ExceptionResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
+        return ExceptionResponse.builder(HttpStatus.BAD_REQUEST)
+                .build(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
+    }
+    @ExceptionHandler
+    @ResponseStatus(HttpStatus.NOT_FOUND)
+    ExceptionResponse handleNoSuchElementException(NoSuchElementException e) {
+        return ExceptionResponse.builder(HttpStatus.NOT_FOUND)
+                .build(e.getMessage() + " does not exist.");
+    }
+}