Fix PodStatus Validator failing in K8sParticipant 58/130958/3
authorFrancescoFioraEst <francesco.fiora@est.tech>
Tue, 13 Sep 2022 14:54:10 +0000 (15:54 +0100)
committerFrancescoFioraEst <francesco.fiora@est.tech>
Tue, 20 Sep 2022 09:00:49 +0000 (10:00 +0100)
Fix PodStatus Validator failing in K8sParticipant using service template
for Test and Verification of ACM State Management.

Issue-ID: POLICY-4355
Change-Id: I63f8ed2c4991422dd43749151387ff54ba7d6071
Signed-off-by: FrancescoFioraEst <francesco.fiora@est.tech>
examples/src/main/resources/clamp/acm/acelement-helm/acelement/resources/config/application.yaml
examples/src/main/resources/clamp/acm/acelement-helm/acelement/resources/config/logback.xml [new file with mode: 0644]
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/acm/participant/kubernetes/helm/PodStatusValidator.java
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/acm/participant/kubernetes/models/ChartInfo.java
participant/participant-impl/participant-impl-kubernetes/src/test/java/org/onap/policy/clamp/acm/participant/kubernetes/helm/PodStatusValidatorTest.java
participant/participant-impl/participant-impl-kubernetes/src/test/resources/ChartList.json

diff --git a/examples/src/main/resources/clamp/acm/acelement-helm/acelement/resources/config/logback.xml b/examples/src/main/resources/clamp/acm/acelement-helm/acelement/resources/config/logback.xml
new file mode 100644 (file)
index 0000000..d013c09
--- /dev/null
@@ -0,0 +1,206 @@
+<!--
+  ============LICENSE_START=======================================================
+  policy-clamp
+  ================================================================================
+  Copyright (C) 2022 Nordix Foundation.
+  ================================================================================
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ============LICENSE_END=========================================================
+  -->
+
+<configuration scan="true" scanPeriod="30 seconds" debug="false">
+
+    <property name="logDir" value="${POLICY_LOGS}" />
+
+    <property name="errorLog" value="error" />
+    <property name="debugLog" value="debug" />
+    <property name="networkLog" value="network" />
+
+    <property name="metricLog" value="metric" />
+    <property name="transactionLog" value="audit" />
+
+    <property name="debugPattern"
+        value="[%d{yyyy-MM-dd'T'HH:mm:ss.SSS+00:00, UTC}|%level|%logger{0}|%thread] %msg%n" />
+    <property name="errorPattern" value="${debugPattern}" />
+    <property name="networkPattern"
+        value="[%d{yyyy-MM-dd'T'HH:mm:ss.SSS+00:00, UTC}|%t]%m%n" />
+
+    <property
+        name="mpPreamble"
+        value="%X{RequestID}|%X{InvocationID}|%X{ServiceName}|%X{PartnerName}" />
+
+    <property
+        name="mpTime"
+        value="%X{BeginTimestamp}|%X{EndTimestamp}|%X{ElapsedTime}" />
+
+    <property
+        name="mpLine1"
+        value="%X{ServiceInstanceID}|%X{VirtualServerName}|%X{StatusCode}|%X{ResponseCode}|%X{ResponseDescription}" />
+
+    <property
+        name="mpLine2"
+        value="%X{InstanceUUID}|%X{Severity}|%X{TargetEntity}|%X{TargetServiceName}|%X{Server}|%X{ServerIPAddress}" />
+
+    <property
+        name="mpLine3"
+        value="%X{ServerFQDN}|%X{ClientIPAddress}|%X{ProcessKey}|%X{RemoteHost}|%X{AlertSeverity}" />
+
+    <property
+        name="mpLine4"
+        value="%X{TargetVirtualEntity}|%level|%thread| %msg%n" />
+
+
+    <property name="metricPattern"
+        value="{$mpPreamble}|{$mpTime}|{$mpLine1}|{$mpLine2}|{$mpLine3}|$mpLine4" />
+
+    <property name="transactionPattern" value="${metricPattern}" />
+
+    <appender name="ErrorOut"
+        class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${logDir}/${errorLog}.log</file>
+        <rollingPolicy
+            class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>${logDir}/${errorLog}.%d{yyyy-MM-dd}.%i.log.zip
+            </fileNamePattern>
+            <maxFileSize>50MB</maxFileSize>
+            <maxHistory>30</maxHistory>
+            <totalSizeCap>10GB</totalSizeCap>
+        </rollingPolicy>
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>WARN</level>
+        </filter>
+        <encoder>
+            <pattern>${errorPattern}</pattern>
+        </encoder>
+    </appender>
+
+    <appender name="AsyncErrorOut"
+        class="ch.qos.logback.classic.AsyncAppender">
+        <appender-ref ref="ErrorOut" />
+    </appender>
+
+    <appender name="DebugOut"
+        class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${logDir}/${debugLog}.log</file>
+        <rollingPolicy
+            class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>${logDir}/${debugLog}.%d{yyyy-MM-dd}.%i.log.zip
+            </fileNamePattern>
+            <maxFileSize>50MB</maxFileSize>
+            <maxHistory>30</maxHistory>
+            <totalSizeCap>10GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${debugPattern}</pattern>
+        </encoder>
+    </appender>
+
+    <appender name="AsyncDebugOut"
+        class="ch.qos.logback.classic.AsyncAppender">
+        <appender-ref ref="DebugOut" />
+    </appender>
+
+    <appender name="NetworkOut"
+        class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${logDir}/${networkLog}.log</file>
+        <rollingPolicy
+            class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>${logDir}/${networkLog}.%d{yyyy-MM-dd}.%i.log.zip
+            </fileNamePattern>
+            <maxFileSize>50MB</maxFileSize>
+            <maxHistory>30</maxHistory>
+            <totalSizeCap>10GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${networkPattern}</pattern>
+        </encoder>
+    </appender>
+
+    <appender name="AsyncNetworkOut"
+        class="ch.qos.logback.classic.AsyncAppender">
+        <appender-ref ref="NetworkOut" />
+    </appender>
+
+    <appender name="MetricOut"
+        class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${logDir}/${metricLog}.log</file>
+        <rollingPolicy
+            class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>${logDir}/${metricLog}.%d{yyyy-MM-dd}.%i.log.zip
+            </fileNamePattern>
+            <maxFileSize>50MB</maxFileSize>
+            <maxHistory>30</maxHistory>
+            <totalSizeCap>10GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${metricPattern}</pattern>
+        </encoder>
+    </appender>
+
+    <appender name="AsyncMetricOut"
+        class="ch.qos.logback.classic.AsyncAppender">
+        <appender-ref ref="MetricOut" />
+    </appender>
+
+    <appender name="TransactionOut"
+        class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${logDir}/${transactionLog}.log</file>
+        <rollingPolicy
+            class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>${logDir}/${transactionLog}.%d{yyyy-MM-dd}.%i.log.zip
+            </fileNamePattern>
+            <maxFileSize>50MB</maxFileSize>
+            <maxHistory>30</maxHistory>
+            <totalSizeCap>10GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${transactionPattern}</pattern>
+        </encoder>
+    </appender>
+
+    <appender name="AsyncTransactionOut"
+        class="ch.qos.logback.classic.AsyncAppender">
+        <appender-ref ref="TransactionOut" />
+    </appender>
+
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <Pattern>[%d{yyyy-MM-dd'T'HH:mm:ss.SSS+00:00, UTC}|%level|%logger{0}|%thread] %msg%n</Pattern>
+        </encoder>
+    </appender>
+
+    <appender name="AsyncStdOut" class="ch.qos.logback.classic.AsyncAppender">
+        <appender-ref ref="STDOUT" />
+    </appender>
+
+    <logger name="network" level="INFO" additivity="false">
+        <appender-ref ref="AsyncNetworkOut" />
+        <appender-ref ref="AsyncStdOut" />
+    </logger>
+
+    <logger name="org.eclipse.jetty.server.RequestLog" level="info" additivity="false">
+        <appender-ref ref="AsyncNetworkOut" />
+        <appender-ref ref="AsyncStdOut" />
+    </logger>
+
+    <logger name="org.eclipse.jetty" level="ERROR" />
+
+    <root level="INFO">
+        <appender-ref ref="AsyncDebugOut" />
+        <appender-ref ref="AsyncErrorOut" />
+        <appender-ref ref="AsyncMetricOut" />
+        <appender-ref ref="AsyncTransactionOut" />
+        <appender-ref ref="AsyncStdOut" />
+    </root>
+
+</configuration>
index 8267bda..67bdc0b 100644 (file)
@@ -1,6 +1,6 @@
 /*-
  * ========================LICENSE_START=================================
- * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * Copyright (C) 2021-2022 Nordix Foundation. All rights reserved.
  * ======================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import java.util.HashMap;
 import java.util.Map;
 import lombok.SneakyThrows;
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.onap.policy.clamp.acm.participant.kubernetes.exception.ServiceException;
 import org.onap.policy.clamp.acm.participant.kubernetes.handler.AutomationCompositionElementHandler;
 import org.onap.policy.clamp.acm.participant.kubernetes.models.ChartInfo;
@@ -62,58 +63,64 @@ public class PodStatusValidator implements Runnable {
     @Override
     public void run() {
         logger.info("Polling the status of deployed pods for the chart {}", chart.getChartId().getName());
-        Map<String, String> podStatusMap;
-        String output = null;
+
+        try {
+            verifyPodStatus();
+        } catch (ServiceException | IOException e) {
+            throw new ServiceException("Error verifying the status of the pod. Exiting", e);
+        }
+    }
+
+    private void verifyPodStatus() throws ServiceException, IOException, InterruptedException {
         var isVerified = false;
         long endTime = System.currentTimeMillis() + (timeout * 1000L);
 
         while (!isVerified && System.currentTimeMillis() < endTime) {
-            try {
-                output = HelmClient.executeCommand(verifyPodStatusCommand(chart));
-                podStatusMap = mapPodStatus(output);
-                isVerified = podStatusMap.values()
-                    .stream()
-                    .allMatch("Running"::equals);
-                if (! isVerified) {
-                    logger.info("Waiting for the pods to be active for the chart {}", chart.getChartId().getName());
-                    podStatusMap.forEach((key, value) -> logger.info("Pod: {} , state: {}", key, value));
-                    AutomationCompositionElementHandler.getPodStatusMap().put(chart.getReleaseName(), podStatusMap);
-                    // Recheck status of pods in specific intervals.
-                    Thread.sleep(statusCheckInterval * 1000L);
-                } else {
-                    logger.info("All pods are in running state for the helm chart {}", chart.getChartId().getName());
-                    AutomationCompositionElementHandler.getPodStatusMap().put(chart.getReleaseName(), podStatusMap);
-                }
-            } catch (ServiceException | IOException  e) {
-                throw new ServiceException("Error verifying the status of the pod. Exiting", e);
+            var output = HelmClient.executeCommand(verifyPodStatusCommand(chart));
+            var podStatusMap = mapPodStatus(output);
+            isVerified = !podStatusMap.isEmpty()
+                    && podStatusMap.values().stream().allMatch("Running"::equals);
+            if (!isVerified) {
+                logger.info("Waiting for the pods to be active for the chart {}", chart.getChartId().getName());
+                podStatusMap.forEach((key, value) -> logger.info("Pod: {} , state: {}", key, value));
+                // Recheck status of pods in specific intervals.
+                Thread.sleep(statusCheckInterval * 1000L);
+            } else {
+                logger.info("All pods are in running state for the helm chart {}", chart.getChartId().getName());
+                AutomationCompositionElementHandler.getPodStatusMap().put(chart.getReleaseName(), podStatusMap);
             }
         }
+        if (!isVerified) {
+            throw new ServiceException("Time out Exception verifying the status of the pod");
+        }
     }
 
     private ProcessBuilder verifyPodStatusCommand(ChartInfo chart) {
-        String cmd = "kubectl get pods --namespace " +  chart.getNamespace() + " | grep "
-                + chart.getChartId().getName();
+        String cmd = "kubectl get pods --namespace " + chart.getNamespace() + " | grep " + getPodName();
         return new ProcessBuilder("sh", "-c", cmd);
     }
 
+    private String getPodName() {
+        return StringUtils.isNotEmpty(chart.getPodName()) ? chart.getPodName() : chart.getChartId().getName();
+    }
 
-    private Map<String, String> mapPodStatus(String output) throws IOException, ServiceException {
+    private Map<String, String> mapPodStatus(String output) throws IOException {
         Map<String, String> podStatusMap = new HashMap<>();
+        var podName = getPodName();
         try (var reader = new BufferedReader(new InputStreamReader(IOUtils.toInputStream(output,
             StandardCharsets.UTF_8)))) {
             var line = reader.readLine();
             while (line != null) {
-                if (line.contains(chart.getChartId().getName())) {
+                if (line.contains(podName)) {
                     var result = line.split("\\s+");
                     podStatusMap.put(result[0], result[2]);
                 }
                 line = reader.readLine();
             }
         }
-        if (!podStatusMap.isEmpty()) {
-            return podStatusMap;
-        } else {
-            throw new ServiceException("Status of Pod is empty");
+        if (podStatusMap.isEmpty()) {
+            logger.warn("Status of  Pod {} is empty", podName);
         }
+        return podStatusMap;
     }
 }
index fbddf8b..962744d 100644 (file)
@@ -35,7 +35,6 @@ import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.InjectMocks;
 import org.mockito.MockedStatic;
 import org.onap.policy.clamp.acm.participant.kubernetes.exception.ServiceException;
 import org.onap.policy.clamp.acm.participant.kubernetes.handler.AutomationCompositionElementHandler;
@@ -51,20 +50,16 @@ class PodStatusValidatorTest {
 
     private static final Coder CODER = new StandardCoder();
     private static final String CHART_INFO_YAML = "src/test/resources/ChartList.json";
+    private static int TIMEOUT = 2;
+    private static int STATUS_CHECK_INTERVAL = 1;
     private static List<ChartInfo> charts;
 
-    @InjectMocks
-    private static PodStatusValidator podStatusValidator;
-
     private static MockedStatic<HelmClient> mockedClient;
 
     @BeforeAll
     static void init() throws CoderException {
         charts = CODER.decode(new File(CHART_INFO_YAML), ChartList.class).getCharts();
         mockedClient = mockStatic(HelmClient.class);
-        int timeout = 60;
-        int statusCheckInterval = 30;
-        podStatusValidator = new PodStatusValidator(charts.get(0), timeout, statusCheckInterval);
     }
 
     @AfterEach
@@ -77,12 +72,12 @@ class PodStatusValidatorTest {
         mockedClient.close();
     }
 
-
     @Test
     void test_RunningPodState() {
         String runningPod = "NAME\tREADY\tSTATUS\tRESTARTS\tAGE\r\nHelloWorld-54777df9f8-qpzqr\t1/1\tRunning\t0\t9h";
         mockedClient.when(() -> HelmClient.executeCommand(any()))
             .thenReturn(runningPod);
+        var podStatusValidator = new PodStatusValidator(charts.get(0), TIMEOUT, STATUS_CHECK_INTERVAL);
         assertDoesNotThrow(() -> podStatusValidator.run());
         assertThat(AutomationCompositionElementHandler.getPodStatusMap()).hasSize(1);
         assertThat(AutomationCompositionElementHandler.getPodStatusMap()).containsKey(charts.get(0).getReleaseName());
@@ -90,15 +85,28 @@ class PodStatusValidatorTest {
             .containsValue(Map.of("HelloWorld-54777df9f8-qpzqr", "Running"));
     }
 
-
     @Test
     void test_InvalidPodState() {
         String invalidPod = "NAME\tREADY\tSTATUS\tRESTARTS\tAGE\nhellofromdocker-54777df9f8-qpzqr\t1/1\tInit\t0\t9h";
         mockedClient.when(() -> HelmClient.executeCommand(any()))
             .thenReturn(invalidPod);
+        var podStatusValidator = new PodStatusValidator(charts.get(1), TIMEOUT, STATUS_CHECK_INTERVAL);
         assertThatThrownBy(() -> podStatusValidator.run())
             .isInstanceOf(ServiceException.class).hasMessage("Error verifying the status of the pod. Exiting");
         assertThat(AutomationCompositionElementHandler.getPodStatusMap()).isEmpty();
     }
 
+    // Use case scenario: Hard coded pod name
+    @Test
+    void test_RunningPodStateWhitPodName() {
+        String runningPod = "NAME\tREADY\tSTATUS\tRESTARTS\tAGE\r\nhelloallworld-54777df9f8-qpzqr\t1/1\tRunning\t0\t9h";
+        mockedClient.when(() -> HelmClient.executeCommand(any()))
+            .thenReturn(runningPod);
+        var podStatusValidator = new PodStatusValidator(charts.get(2), TIMEOUT, STATUS_CHECK_INTERVAL);
+        assertDoesNotThrow(() -> podStatusValidator.run());
+        assertThat(AutomationCompositionElementHandler.getPodStatusMap()).hasSize(1);
+        assertThat(AutomationCompositionElementHandler.getPodStatusMap()).containsKey(charts.get(2).getReleaseName());
+        assertThat(AutomationCompositionElementHandler.getPodStatusMap())
+            .containsValue(Map.of("helloallworld-54777df9f8-qpzqr", "Running"));
+    }
 }
index d4cd5de..f580496 100644 (file)
       },
       "namespace" : "onap",
       "releaseName" : "nginxapp"
+    },
+    {
+      "chartId" : {
+        "name" : "HelloWorld",
+        "version" :   "1.2"
+      },
+      "namespace" : "onap",
+      "repository" : {
+        "repoName": "chartMuseum",
+        "address" : "https://localhost:8080"
+      },
+      "releaseName" : "helloworld",
+      "podName" : "helloallworld"
     }
   ]
 }