4731c24b731758db81057cbbfe643da973c09c74
[so/adapters/so-cnf-adapter.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2023 Nordix Foundation.
4  * ================================================================================
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  * SPDX-License-Identifier: Apache-2.0
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.so.cnfm.lcm.bpmn.flows.extclients.kubernetes;
22
23 import static org.onap.so.cnfm.lcm.bpmn.flows.Constants.KIND_DAEMON_SET;
24 import static org.onap.so.cnfm.lcm.bpmn.flows.Constants.KIND_DEPLOYMENT;
25 import static org.onap.so.cnfm.lcm.bpmn.flows.Constants.KIND_JOB;
26 import static org.onap.so.cnfm.lcm.bpmn.flows.Constants.KIND_POD;
27 import static org.onap.so.cnfm.lcm.bpmn.flows.Constants.KIND_REPLICA_SET;
28 import static org.onap.so.cnfm.lcm.bpmn.flows.Constants.KIND_SERVICE;
29 import static org.onap.so.cnfm.lcm.bpmn.flows.Constants.KIND_STATEFUL_SET;
30 import java.io.IOException;
31 import java.lang.reflect.Type;
32 import java.net.SocketTimeoutException;
33 import java.util.ArrayList;
34 import java.util.Collections;
35 import java.util.HashMap;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Map.Entry;
39 import java.util.Optional;
40 import java.util.function.Predicate;
41 import java.util.stream.Collectors;
42 import org.onap.so.cnfm.lcm.bpmn.flows.exceptions.KubernetesRequestProcessingException;
43 import org.onap.so.cnfm.lcm.bpmn.flows.exceptions.KubernetesRequestTimeOut;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46 import org.springframework.beans.factory.annotation.Value;
47 import org.springframework.stereotype.Service;
48 import com.google.gson.reflect.TypeToken;
49 import io.kubernetes.client.apimachinery.GroupVersion;
50 import io.kubernetes.client.common.KubernetesListObject;
51 import io.kubernetes.client.common.KubernetesObject;
52 import io.kubernetes.client.custom.IntOrString;
53 import io.kubernetes.client.openapi.ApiClient;
54 import io.kubernetes.client.openapi.ApiException;
55 import io.kubernetes.client.openapi.apis.AppsV1Api;
56 import io.kubernetes.client.openapi.apis.BatchV1Api;
57 import io.kubernetes.client.openapi.apis.CoreV1Api;
58 import io.kubernetes.client.openapi.models.V1DaemonSet;
59 import io.kubernetes.client.openapi.models.V1DaemonSetList;
60 import io.kubernetes.client.openapi.models.V1DaemonSetSpec;
61 import io.kubernetes.client.openapi.models.V1DaemonSetStatus;
62 import io.kubernetes.client.openapi.models.V1Deployment;
63 import io.kubernetes.client.openapi.models.V1DeploymentList;
64 import io.kubernetes.client.openapi.models.V1DeploymentSpec;
65 import io.kubernetes.client.openapi.models.V1DeploymentStatus;
66 import io.kubernetes.client.openapi.models.V1Job;
67 import io.kubernetes.client.openapi.models.V1JobCondition;
68 import io.kubernetes.client.openapi.models.V1JobList;
69 import io.kubernetes.client.openapi.models.V1ObjectMeta;
70 import io.kubernetes.client.openapi.models.V1Pod;
71 import io.kubernetes.client.openapi.models.V1PodCondition;
72 import io.kubernetes.client.openapi.models.V1PodList;
73 import io.kubernetes.client.openapi.models.V1ReplicaSet;
74 import io.kubernetes.client.openapi.models.V1ReplicaSetList;
75 import io.kubernetes.client.openapi.models.V1ReplicaSetSpec;
76 import io.kubernetes.client.openapi.models.V1ReplicaSetStatus;
77 import io.kubernetes.client.openapi.models.V1RollingUpdateStatefulSetStrategy;
78 import io.kubernetes.client.openapi.models.V1Service;
79 import io.kubernetes.client.openapi.models.V1ServiceList;
80 import io.kubernetes.client.openapi.models.V1StatefulSet;
81 import io.kubernetes.client.openapi.models.V1StatefulSetList;
82 import io.kubernetes.client.openapi.models.V1StatefulSetSpec;
83 import io.kubernetes.client.openapi.models.V1StatefulSetStatus;
84 import io.kubernetes.client.openapi.models.V1StatefulSetUpdateStrategy;
85 import io.kubernetes.client.util.Watch;
86 import io.kubernetes.client.util.Watch.Response;
87 import okhttp3.Call;
88
89 /**
90  *
91  * @author Waqas Ikram (waqas.ikram@est.tech)
92  *
93  */
94 @Service
95 public class KubernetesClientImpl implements KubernetesClient {
96     private static final String ROLLING_UPDATE = "RollingUpdate";
97     private static final String EVENT_TYPE_ERROR = "ERROR";
98     private static final String EVENT_TYPE_DELETED = "DELETED";
99     private static final String EVENT_TYPE_MODIFIED = "MODIFIED";
100     private static final String EVENT_TYPE_ADDED = "ADDED";
101     private static final String TRUE_STRING = Boolean.TRUE.toString();
102     private static final String JOB_FAILED = "Failed";
103     private static final String JOB_COMPLETE = "Complete";
104     private static final boolean DISABLE_WATCH = false;
105     private static final boolean ENABLE_WATCH = true;
106     private static final String POD_READY = "Ready";
107
108     private static final Logger logger = LoggerFactory.getLogger(KubernetesClientImpl.class);
109
110     /**
111      * Event Listener timeout in seconds Note: this should be less then the timeout camunda task execution
112      */
113     @Value("${kubernetes.client.http-request.timeoutSeconds:5}")
114     private Integer timeoutSeconds;
115
116     @Override
117     public boolean isJobReady(final ApiClient apiClient, final String labelSelector)
118             throws KubernetesRequestProcessingException {
119         logger.debug("Will check if Job is ready using labelSelector: {}", labelSelector);
120         try {
121             final BatchV1Api batchV1Api = new BatchV1Api(apiClient);
122             final Call call = batchV1Api.listJobForAllNamespacesCall(null, null, null, labelSelector, null, null, null,
123                     null, timeoutSeconds, ENABLE_WATCH, null);
124
125             final Map<V1Job, String> readyResources =
126                     getReadyResources(apiClient, call, new TypeToken<Response<V1Job>>() {}.getType());
127
128             if (!readyResources.isEmpty()) {
129                 final List<Entry<V1Job, String>> jobNotReadyList = readyResources.entrySet().stream()
130                         .filter(entry -> !isResourceReady(entry.getKey(), entry.getValue(), this::isJobReady))
131                         .collect(Collectors.toList());
132
133                 if (jobNotReadyList.isEmpty()) {
134                     logger.debug("JobList is ready ...");
135                     return true;
136                 }
137                 logger.debug("JobList is not yet Ready: {}", jobNotReadyList);
138                 return false;
139
140             }
141
142             logger.warn("No items found in jobList : {}", readyResources);
143             return false;
144
145         } catch (final ApiException exception) {
146             handleApiException(KIND_JOB, labelSelector, exception);
147         } catch (final RuntimeException runtimeException) {
148             handleRuntimeException(KIND_JOB, labelSelector, runtimeException);
149         }
150         logger.debug("Returning false as Job is not ready ...");
151         return false;
152     }
153
154     @Override
155     public boolean isPodReady(final ApiClient apiClient, final String labelSelector)
156             throws KubernetesRequestProcessingException {
157         logger.debug("Will check if Pod is ready using labelSelector: {}", labelSelector);
158         try {
159             final CoreV1Api coreV1Api = new CoreV1Api(apiClient);
160             final Call call = coreV1Api.listPodForAllNamespacesCall(null, null, null, labelSelector, null, null, null,
161                     null, timeoutSeconds, ENABLE_WATCH, null);
162
163             final Map<V1Pod, String> readyResources =
164                     getReadyResources(apiClient, call, new TypeToken<Response<V1Pod>>() {}.getType());
165
166             if (!readyResources.isEmpty()) {
167                 final List<Entry<V1Pod, String>> podNotReadyList = readyResources.entrySet().stream()
168                         .filter(entry -> !isResourceReady(entry.getKey(), entry.getValue(), this::isPodReady))
169                         .collect(Collectors.toList());
170
171                 if (podNotReadyList.isEmpty()) {
172                     logger.debug("PodList is ready ...");
173                     return true;
174                 }
175                 logger.debug("PodList is not yet Ready: {}", podNotReadyList);
176                 return false;
177
178             }
179
180             logger.warn("No items found in podList : {}", readyResources);
181             return false;
182
183         } catch (final ApiException exception) {
184             handleApiException(KIND_POD, labelSelector, exception);
185         } catch (final RuntimeException runtimeException) {
186             handleRuntimeException(KIND_POD, labelSelector, runtimeException);
187         }
188
189         logger.debug("Returning false as Pod is not ready ...");
190
191         return false;
192     }
193
194     @Override
195     public boolean isServiceReady(final ApiClient apiClient, final String labelSelector)
196             throws KubernetesRequestProcessingException {
197         logger.debug("Will check if Service is ready using labelSelector: {}", labelSelector);
198         try {
199             final CoreV1Api api = new CoreV1Api(apiClient);
200             final Call call = api.listServiceForAllNamespacesCall(null, null, null, labelSelector, null, null, null,
201                     null, timeoutSeconds, ENABLE_WATCH, null);
202
203             final Map<V1Service, String> readyResources =
204                     getReadyResources(apiClient, call, new TypeToken<Response<V1Service>>() {}.getType());
205
206             if (!readyResources.isEmpty()) {
207                 final List<Entry<V1Service, String>> serviceNotReadyList = readyResources.entrySet().stream()
208                         .filter(entry -> !isResourceReady(entry.getKey(), entry.getValue(), this::isServiceReady))
209                         .collect(Collectors.toList());
210
211                 if (serviceNotReadyList.isEmpty()) {
212                     logger.debug("ServiceList is ready ...");
213                     return true;
214                 }
215                 logger.debug("ServiceList is not yet Ready: {}", serviceNotReadyList);
216                 return false;
217
218             }
219
220             logger.warn("No items found in serviceList : {}", readyResources);
221             return false;
222
223         } catch (final ApiException exception) {
224             handleApiException(KIND_SERVICE, labelSelector, exception);
225         } catch (final RuntimeException runtimeException) {
226             handleRuntimeException(KIND_SERVICE, labelSelector, runtimeException);
227         }
228
229         logger.debug("Returning false as Service is not ready ...");
230         return false;
231     }
232
233     @Override
234     public boolean isDeploymentReady(final ApiClient apiClient, final String labelSelector)
235             throws KubernetesRequestProcessingException {
236         logger.debug("Will check if Deployment is ready using labelSelector: {}", labelSelector);
237
238         try {
239             final AppsV1Api appsV1Api = new AppsV1Api(apiClient);
240             final Call call = appsV1Api.listDeploymentForAllNamespacesCall(null, null, null, labelSelector, null, null,
241                     null, null, timeoutSeconds, ENABLE_WATCH, null);
242
243             final Map<V1Deployment, String> readyResources =
244                     getReadyResources(apiClient, call, new TypeToken<Response<V1Deployment>>() {}.getType());
245
246             if (!readyResources.isEmpty()) {
247                 final List<Entry<V1Deployment, String>> deploymentNotReadyList = readyResources.entrySet().stream()
248                         .filter(entry -> !isResourceReady(entry.getKey(), entry.getValue(), this::isDeploymentReady))
249                         .collect(Collectors.toList());
250
251                 if (deploymentNotReadyList.isEmpty()) {
252                     logger.debug("DeploymentList is ready ...");
253                     return true;
254                 }
255                 logger.debug("DeploymentList is not yet Ready: {}", deploymentNotReadyList);
256                 return false;
257
258             }
259
260             logger.warn("No items found in deploymentList : {}", readyResources);
261             return false;
262
263         } catch (final ApiException exception) {
264             handleApiException(KIND_DEPLOYMENT, labelSelector, exception);
265         } catch (final RuntimeException runtimeException) {
266             handleRuntimeException(KIND_DEPLOYMENT, labelSelector, runtimeException);
267         }
268
269         logger.debug("Returning false as Deployment is not ready ...");
270         return false;
271     }
272
273     @Override
274     public boolean isReplicaSetReady(final ApiClient apiClient, final String labelSelector)
275             throws KubernetesRequestProcessingException {
276         logger.debug("Will check if ReplicaSet is ready using labelSelector: {}", labelSelector);
277         try {
278             final AppsV1Api appsV1Api = new AppsV1Api(apiClient);
279             final Call call = appsV1Api.listReplicaSetForAllNamespacesCall(null, null, null, labelSelector, null, null,
280                     null, null, timeoutSeconds, ENABLE_WATCH, null);
281
282             final Map<V1ReplicaSet, String> readyResources =
283                     getReadyResources(apiClient, call, new TypeToken<Response<V1ReplicaSet>>() {}.getType());
284
285             if (!readyResources.isEmpty()) {
286                 final List<Entry<V1ReplicaSet, String>> replicaSet = readyResources.entrySet().stream()
287                         .filter(entry -> !isResourceReady(entry.getKey(), entry.getValue(), this::isReplicaSetReady))
288                         .collect(Collectors.toList());
289
290                 if (replicaSet.isEmpty()) {
291                     logger.debug("ReplicaSetList is ready ...");
292                     return true;
293                 }
294                 logger.debug("ReplicaSetList is not yet Ready: {}", replicaSet);
295                 return false;
296
297             }
298
299             logger.warn("No items found in replicaSetList : {}", readyResources);
300             return false;
301
302         } catch (final ApiException exception) {
303             handleApiException(KIND_REPLICA_SET, labelSelector, exception);
304         } catch (final RuntimeException runtimeException) {
305             handleRuntimeException(KIND_REPLICA_SET, labelSelector, runtimeException);
306         }
307         logger.debug("Returning false as ReplicaSet is not ready ...");
308         return false;
309     }
310
311     @Override
312     public boolean isDaemonSetReady(final ApiClient apiClient, final String labelSelector)
313             throws KubernetesRequestProcessingException {
314         logger.debug("Will check if DaemonSet is ready using labelSelector: {}", labelSelector);
315         try {
316             final AppsV1Api appsV1Api = new AppsV1Api(apiClient);
317             final Call call = appsV1Api.listDaemonSetForAllNamespacesCall(null, null, null, labelSelector, null, null,
318                     null, null, timeoutSeconds, ENABLE_WATCH, null);
319
320             final Map<V1DaemonSet, String> readyResources =
321                     getReadyResources(apiClient, call, new TypeToken<Response<V1DaemonSet>>() {}.getType());
322
323             if (!readyResources.isEmpty()) {
324                 final List<Entry<V1DaemonSet, String>> daemonSetNotReadyList = readyResources.entrySet().stream()
325                         .filter(entry -> !isResourceReady(entry.getKey(), entry.getValue(), this::isDaemonSetReady))
326                         .collect(Collectors.toList());
327
328                 if (daemonSetNotReadyList.isEmpty()) {
329                     logger.debug("DaemonSetList is ready ...");
330                     return true;
331                 }
332                 logger.debug("DaemonSetList is not yet Ready: {}", daemonSetNotReadyList);
333                 return false;
334
335             }
336
337             logger.warn("No items found in daemonSetList : {}", readyResources);
338             return false;
339
340         } catch (final ApiException exception) {
341             handleApiException(KIND_DAEMON_SET, labelSelector, exception);
342         } catch (final RuntimeException runtimeException) {
343             handleRuntimeException(KIND_DAEMON_SET, labelSelector, runtimeException);
344         }
345         logger.debug("Returning false as DaemonSet is not ready ...");
346         return false;
347     }
348
349     @Override
350     public boolean isStatefulSetReady(final ApiClient apiClient, final String labelSelector)
351             throws KubernetesRequestProcessingException {
352         logger.debug("Will check if StatefulSet is ready using labelSelector: {}", labelSelector);
353         try {
354             final AppsV1Api appsV1Api = new AppsV1Api(apiClient);
355             final Call call = appsV1Api.listStatefulSetForAllNamespacesCall(null, null, null, labelSelector, null, null,
356                     null, null, timeoutSeconds, ENABLE_WATCH, null);
357
358             final Map<V1StatefulSet, String> readyResources =
359                     getReadyResources(apiClient, call, new TypeToken<Response<V1StatefulSet>>() {}.getType());
360
361             if (!readyResources.isEmpty()) {
362                 final List<Entry<V1StatefulSet, String>> statefulSetNotReadyList = readyResources.entrySet().stream()
363                         .filter(entry -> !isResourceReady(entry.getKey(), entry.getValue(), this::isStatefulSetReady))
364                         .collect(Collectors.toList());
365
366                 if (statefulSetNotReadyList.isEmpty()) {
367                     logger.debug("StatefulSetList is ready ...");
368                     return true;
369                 }
370                 logger.debug("StatefulSetList is not yet Ready: {}", statefulSetNotReadyList);
371                 return false;
372
373             }
374
375             logger.warn("No items found in statefulSetList : {}", readyResources);
376             return false;
377
378         } catch (final ApiException exception) {
379             handleApiException(KIND_STATEFUL_SET, labelSelector, exception);
380         } catch (final RuntimeException runtimeException) {
381             handleRuntimeException(KIND_STATEFUL_SET, labelSelector, runtimeException);
382         }
383         logger.debug("Returning false as StatefulSet is not ready ...");
384         return false;
385     }
386
387     @Override
388     public boolean isServiceDeleted(final ApiClient apiClient, final String labelSelector)
389             throws KubernetesRequestProcessingException {
390         logger.debug("Check is Service deleted by using labelSelector: {}", labelSelector);
391         try {
392             final CoreV1Api coreV1Api = new CoreV1Api(apiClient);
393             final V1ServiceList v1ServiceList = coreV1Api.listServiceForAllNamespaces(null, null, null, labelSelector,
394                     null, null, null, null, timeoutSeconds, DISABLE_WATCH);
395             logger.debug("Response from list service for all Namespaces: {}", v1ServiceList);
396             return v1ServiceList.getItems().isEmpty();
397         } catch (final ApiException exception) {
398             logger.debug("Return false because of exception occurred: {}", exception.getMessage());
399             handleApiException(KIND_SERVICE, labelSelector, exception);
400         } catch (final RuntimeException runtimeException) {
401             logger.debug("Return false because of Runtime exception occurred: {}", runtimeException.getMessage());
402             handleRuntimeException(KIND_SERVICE, labelSelector, runtimeException);
403         }
404         logger.debug("Returning false as Service is not Deleted ...");
405         return false;
406     }
407
408     @Override
409     public boolean isPodDeleted(final ApiClient apiClient, final String labelSelector)
410             throws KubernetesRequestProcessingException {
411         logger.debug("Check is Pod deleted by using labelSelector: {}", labelSelector);
412         try {
413             final CoreV1Api coreV1Api = new CoreV1Api(apiClient);
414             final V1PodList v1PodList = coreV1Api.listPodForAllNamespaces(null, null, null, labelSelector, null, null,
415                     null, null, timeoutSeconds, DISABLE_WATCH);
416             logger.debug("Response from list Pod for all Namespaces: {}", v1PodList);
417             return v1PodList.getItems().isEmpty();
418         } catch (final ApiException exception) {
419             logger.debug("Return false because of exception occurred: {}", exception.getMessage());
420             handleApiException(KIND_POD, labelSelector, exception);
421         } catch (final RuntimeException runtimeException) {
422             logger.debug("Return false because of Runtime exception occurred: {}", runtimeException.getMessage());
423             handleRuntimeException(KIND_POD, labelSelector, runtimeException);
424         }
425         logger.debug("Returning false as Pod is not Deleted ...");
426         return false;
427     }
428
429     @Override
430     public boolean isJobDeleted(final ApiClient apiClient, final String labelSelector)
431             throws KubernetesRequestProcessingException {
432         logger.debug("Check is Job deleted by using labelSelector: {}", labelSelector);
433         try {
434             final BatchV1Api batchV1Api = new BatchV1Api(apiClient);
435             final V1JobList v1JobList = batchV1Api.listJobForAllNamespaces(null, null, null, labelSelector, null, null,
436                     null, null, timeoutSeconds, DISABLE_WATCH);
437             logger.debug("Response from list Job for all Namespaces: {}", v1JobList);
438             return v1JobList.getItems().isEmpty();
439         } catch (final ApiException exception) {
440             logger.debug("Return false because of exception occurred: {}", exception.getMessage());
441             handleApiException(KIND_JOB, labelSelector, exception);
442         } catch (final RuntimeException runtimeException) {
443             logger.debug("Return false because of Runtime exception occurred: {}", runtimeException.getMessage());
444             handleRuntimeException(KIND_JOB, labelSelector, runtimeException);
445         }
446         logger.debug("Returning false as Job is not Deleted ...");
447         return false;
448     }
449
450     @Override
451     public boolean isDeploymentDeleted(final ApiClient apiClient, final String labelSelector)
452             throws KubernetesRequestProcessingException {
453         logger.debug("Check is Deployment deleted by using labelSelector: {}", labelSelector);
454         try {
455             final AppsV1Api batchV1Api = new AppsV1Api(apiClient);
456             final V1DeploymentList v1DeploymentList = batchV1Api.listDeploymentForAllNamespaces(null, null, null,
457                     labelSelector, null, null, null, null, timeoutSeconds, DISABLE_WATCH);
458             logger.debug("Response from list Deployment for all Namespaces: {}", v1DeploymentList);
459             return v1DeploymentList.getItems().isEmpty();
460         } catch (final ApiException exception) {
461             logger.debug("Return false because of exception occurred: {}", exception.getMessage());
462             handleApiException(KIND_DEPLOYMENT, labelSelector, exception);
463         } catch (final RuntimeException runtimeException) {
464             logger.debug("Return false because of Runtime exception occurred: {}", runtimeException.getMessage());
465             handleRuntimeException(KIND_DEPLOYMENT, labelSelector, runtimeException);
466         }
467         logger.debug("Returning false as Deployment is not Deleted ...");
468         return false;
469     }
470
471     @Override
472     public boolean isReplicaSetDeleted(final ApiClient apiClient, final String labelSelector)
473             throws KubernetesRequestProcessingException {
474         logger.debug("Check is ReplicaSet deleted by using labelSelector: {}", labelSelector);
475         try {
476             final AppsV1Api batchV1Api = new AppsV1Api(apiClient);
477             final V1ReplicaSetList v1ReplicaSetList = batchV1Api.listReplicaSetForAllNamespaces(null, null, null,
478                     labelSelector, null, null, null, null, timeoutSeconds, DISABLE_WATCH);
479             logger.debug("Response from list ReplicaSet for all Namespaces: {}", v1ReplicaSetList);
480             return v1ReplicaSetList.getItems().isEmpty();
481         } catch (final ApiException exception) {
482             logger.debug("Return false because of exception occurred: {}", exception.getMessage());
483             handleApiException(KIND_REPLICA_SET, labelSelector, exception);
484         } catch (final RuntimeException runtimeException) {
485             logger.debug("Return false because of Runtime exception occurred: {}", runtimeException.getMessage());
486             handleRuntimeException(KIND_REPLICA_SET, labelSelector, runtimeException);
487         }
488         logger.debug("Returning false as ReplicaSet is not Deleted ...");
489         return false;
490     }
491
492     @Override
493     public boolean isDaemonSetDeleted(final ApiClient apiClient, final String labelSelector)
494             throws KubernetesRequestProcessingException {
495         logger.debug("Check is DaemonSet deleted by using labelSelector: {}", labelSelector);
496         try {
497             final AppsV1Api batchV1Api = new AppsV1Api(apiClient);
498             final V1DaemonSetList v1DaemonSetList = batchV1Api.listDaemonSetForAllNamespaces(null, null, null,
499                     labelSelector, null, null, null, null, timeoutSeconds, DISABLE_WATCH);
500             logger.debug("Response from list DaemonSet for all Namespaces: {}", v1DaemonSetList);
501             return v1DaemonSetList.getItems().isEmpty();
502         } catch (final ApiException exception) {
503             logger.debug("Return false because of exception occurred: {}", exception.getMessage());
504             handleApiException(KIND_DAEMON_SET, labelSelector, exception);
505         } catch (final RuntimeException runtimeException) {
506             logger.debug("Return false because of Runtime exception occurred: {}", runtimeException.getMessage());
507             handleRuntimeException(KIND_DAEMON_SET, labelSelector, runtimeException);
508         }
509         logger.debug("Returning false as DaemonSet is not Deleted ...");
510         return false;
511     }
512
513     @Override
514     public boolean isStatefulSetDeleted(final ApiClient apiClient, final String labelSelector)
515             throws KubernetesRequestProcessingException {
516         logger.debug("Check is StatefulSet deleted by using labelSelector: {}", labelSelector);
517         try {
518             final AppsV1Api batchV1Api = new AppsV1Api(apiClient);
519             final V1StatefulSetList v1StatefulSetList = batchV1Api.listStatefulSetForAllNamespaces(null, null, null,
520                     labelSelector, null, null, null, null, timeoutSeconds, DISABLE_WATCH);
521             logger.debug("Response from list StatefulSet for all Namespaces: {}", v1StatefulSetList);
522             return v1StatefulSetList.getItems().isEmpty();
523         } catch (final ApiException exception) {
524             logger.debug("Return false because of exception occurred: {}", exception.getMessage());
525             handleApiException(KIND_STATEFUL_SET, labelSelector, exception);
526         } catch (final RuntimeException runtimeException) {
527             logger.debug("Return false because of Runtime exception occurred: {}", runtimeException.getMessage());
528             handleRuntimeException(KIND_STATEFUL_SET, labelSelector, runtimeException);
529         }
530         logger.debug("Returning false as StatefulSet is not Deleted ...");
531         return false;
532     }
533
534
535     @Override
536     public List<KubernetesResource> getJobResources(final ApiClient apiClient, final String labelSelector)
537             throws KubernetesRequestProcessingException {
538         logger.debug("Retrieving Jobs using labelSelector: {}", labelSelector);
539         try {
540             final BatchV1Api batchV1Api = new BatchV1Api(apiClient);
541             final V1JobList jobList = batchV1Api.listJobForAllNamespaces(null, null, null, labelSelector, null, null,
542                     null, null, timeoutSeconds, DISABLE_WATCH);
543
544             logger.debug("Received Jobs: {}", jobList);
545             return getKubernetesResource(jobList);
546
547         } catch (final ApiException exception) {
548             handleApiException(KIND_JOB, labelSelector, exception);
549         } catch (final IllegalArgumentException illegalArgumentException) {
550             handleIllegalArgumentException(KIND_JOB, labelSelector, illegalArgumentException);
551         } catch (final RuntimeException runtimeException) {
552             handleRuntimeException(KIND_JOB, labelSelector, runtimeException);
553         }
554
555         logger.error("Unable to find any job resources ...");
556         return Collections.emptyList();
557     }
558
559     @Override
560     public List<KubernetesResource> getDeploymentResources(final ApiClient apiClient, final String labelSelector)
561             throws KubernetesRequestProcessingException {
562         logger.debug("Retrieving Deployment using labelSelector: {}", labelSelector);
563         try {
564             final AppsV1Api appsV1Api = new AppsV1Api(apiClient);
565             final V1DeploymentList deploymentList = appsV1Api.listDeploymentForAllNamespaces(null, null, null,
566                     labelSelector, null, null, null, null, timeoutSeconds, DISABLE_WATCH);
567
568             logger.debug("Received Deployments: {}", deploymentList);
569             return getKubernetesResource(deploymentList);
570
571         } catch (final ApiException exception) {
572             handleApiException(KIND_DEPLOYMENT, labelSelector, exception);
573         } catch (final IllegalArgumentException illegalArgumentException) {
574             handleIllegalArgumentException(KIND_DEPLOYMENT, labelSelector, illegalArgumentException);
575         } catch (final RuntimeException runtimeException) {
576             handleRuntimeException(KIND_DEPLOYMENT, labelSelector, runtimeException);
577         }
578
579         logger.error("Unable to find any Deployment resources ...");
580         return Collections.emptyList();
581     }
582
583     @Override
584     public List<KubernetesResource> getPodResources(final ApiClient apiClient, final String labelSelector)
585             throws KubernetesRequestProcessingException {
586         logger.debug("Retrieving Pod using labelSelector: {}", labelSelector);
587         try {
588             final CoreV1Api coreV1Api = new CoreV1Api(apiClient);
589             final V1PodList podList = coreV1Api.listPodForAllNamespaces(null, null, null, labelSelector, null, null,
590                     null, null, timeoutSeconds, DISABLE_WATCH);
591
592             logger.debug("Received Pods: {}", podList);
593             return getKubernetesResource(podList);
594
595         } catch (final ApiException exception) {
596             handleApiException(KIND_POD, labelSelector, exception);
597         } catch (final IllegalArgumentException illegalArgumentException) {
598             handleIllegalArgumentException(KIND_POD, labelSelector, illegalArgumentException);
599         } catch (final RuntimeException runtimeException) {
600             handleRuntimeException(KIND_POD, labelSelector, runtimeException);
601         }
602
603         logger.error("Unable to find any Pod resources ...");
604         return Collections.emptyList();
605     }
606
607     @Override
608     public List<KubernetesResource> getServiceResources(final ApiClient apiClient, final String labelSelector)
609             throws KubernetesRequestProcessingException {
610         logger.debug("Retrieving Service using labelSelector: {}", labelSelector);
611         try {
612             final CoreV1Api coreV1Api = new CoreV1Api(apiClient);
613             final V1ServiceList serviceList = coreV1Api.listServiceForAllNamespaces(null, null, null, labelSelector,
614                     null, null, null, null, timeoutSeconds, DISABLE_WATCH);
615
616             logger.debug("Received Services: {}", serviceList);
617             return getKubernetesResource(serviceList);
618
619         } catch (final ApiException exception) {
620             handleApiException(KIND_SERVICE, labelSelector, exception);
621         } catch (final IllegalArgumentException illegalArgumentException) {
622             handleIllegalArgumentException(KIND_SERVICE, labelSelector, illegalArgumentException);
623         } catch (final RuntimeException runtimeException) {
624             handleRuntimeException(KIND_SERVICE, labelSelector, runtimeException);
625         }
626
627         logger.error("Unable to find any Service resources ...");
628         return Collections.emptyList();
629     }
630
631     @Override
632     public List<KubernetesResource> getReplicaSetResources(final ApiClient apiClient, final String labelSelector)
633             throws KubernetesRequestProcessingException {
634         logger.debug("Retrieving ReplicaSet using labelSelector: {}", labelSelector);
635         try {
636             final AppsV1Api appsV1Api = new AppsV1Api(apiClient);
637             final V1ReplicaSetList replicaSetList = appsV1Api.listReplicaSetForAllNamespaces(null, null, null,
638                     labelSelector, null, null, null, null, timeoutSeconds, DISABLE_WATCH);
639
640             logger.debug("Received ReplicaSets: {}", replicaSetList);
641             return getKubernetesResource(replicaSetList);
642
643         } catch (final ApiException exception) {
644             handleApiException(KIND_REPLICA_SET, labelSelector, exception);
645         } catch (final IllegalArgumentException illegalArgumentException) {
646             handleIllegalArgumentException(KIND_REPLICA_SET, labelSelector, illegalArgumentException);
647         } catch (final RuntimeException runtimeException) {
648             handleRuntimeException(KIND_REPLICA_SET, labelSelector, runtimeException);
649         }
650
651         logger.error("Unable to find any ReplicaSet resources ...");
652         return Collections.emptyList();
653     }
654
655     @Override
656     public List<KubernetesResource> getDaemonSetResources(final ApiClient apiClient, final String labelSelector)
657             throws KubernetesRequestProcessingException {
658         logger.debug("Retrieving DaemonSet using labelSelector: {}", labelSelector);
659         try {
660             final AppsV1Api appsV1Api = new AppsV1Api(apiClient);
661
662             final V1DaemonSetList daemonSetList = appsV1Api.listDaemonSetForAllNamespaces(null, null, null,
663                     labelSelector, null, null, null, null, timeoutSeconds, DISABLE_WATCH);
664
665             logger.debug("Received DaemonSets: {}", daemonSetList);
666             return getKubernetesResource(daemonSetList);
667
668         } catch (final ApiException exception) {
669             handleApiException(KIND_DAEMON_SET, labelSelector, exception);
670         } catch (final IllegalArgumentException illegalArgumentException) {
671             handleIllegalArgumentException(KIND_DAEMON_SET, labelSelector, illegalArgumentException);
672         } catch (final RuntimeException runtimeException) {
673             handleRuntimeException(KIND_DAEMON_SET, labelSelector, runtimeException);
674         }
675
676         logger.error("Unable to find any DaemonSet resources ...");
677         return Collections.emptyList();
678     }
679
680     @Override
681     public List<KubernetesResource> getStatefulSetResources(final ApiClient apiClient, final String labelSelector)
682             throws KubernetesRequestProcessingException {
683         logger.debug("Retrieving StatefulSet using labelSelector: {}", labelSelector);
684         try {
685             final AppsV1Api appsV1Api = new AppsV1Api(apiClient);
686
687             final V1StatefulSetList statefulSetList = appsV1Api.listStatefulSetForAllNamespaces(null, null, null,
688                     labelSelector, null, null, null, null, timeoutSeconds, DISABLE_WATCH);
689
690             logger.debug("Received StatefulSets: {}", statefulSetList);
691             return getKubernetesResource(statefulSetList);
692
693         } catch (final ApiException exception) {
694             handleApiException(KIND_STATEFUL_SET, labelSelector, exception);
695         } catch (final IllegalArgumentException illegalArgumentException) {
696             handleIllegalArgumentException(KIND_STATEFUL_SET, labelSelector, illegalArgumentException);
697         } catch (final RuntimeException runtimeException) {
698             handleRuntimeException(KIND_STATEFUL_SET, labelSelector, runtimeException);
699         }
700
701         logger.error("Unable to find any StatefulSet resources ...");
702         return Collections.emptyList();
703
704     }
705
706     private List<KubernetesResource> getKubernetesResource(final KubernetesListObject kubernetesListObject) {
707         if (kubernetesListObject != null && kubernetesListObject.getItems() != null) {
708             final List<KubernetesResource> kubernetesResources = new ArrayList<>();
709             final List<? extends KubernetesObject> items = kubernetesListObject.getItems();
710             items.forEach(item -> {
711                 final String apiVersion =
712                         item.getApiVersion() != null ? item.getApiVersion() : kubernetesListObject.getApiVersion();
713                 final String kind = item.getKind() != null ? item.getKind() : kubernetesListObject.getKind();
714                 kubernetesResources.add(getKubernetesResource(apiVersion, kind, item.getMetadata()));
715             });
716             logger.debug("KubernetesResources found: {}", kubernetesResources);
717             return kubernetesResources;
718         }
719         logger.error("kubernetesListObject or items is null {}", kubernetesListObject);
720         return Collections.emptyList();
721     }
722
723     private KubernetesResource getKubernetesResource(final String apiVersion, final String kind,
724             final V1ObjectMeta metadata) {
725         final GroupVersion groupVersion = GroupVersion.parse(apiVersion);
726         final KubernetesResource resource =
727                 new KubernetesResource().id(metadata.getUid()).name(metadata.getName()).group(groupVersion.getGroup())
728                         .version(groupVersion.getVersion()).kind(kind).resourceVersion(metadata.getResourceVersion())
729                         .namespace(metadata.getNamespace() != null ? metadata.getNamespace() : "")
730                         .labels(getLabels(metadata.getLabels()));
731         return resource;
732     }
733
734     private List<String> getLabels(final Map<String, String> labels) {
735         if (labels != null) {
736             final List<String> result = new ArrayList<>();
737             labels.entrySet().forEach(entry -> {
738                 result.add(entry.getKey() + "=" + entry.getValue());
739             });
740             return result;
741         }
742         return Collections.emptyList();
743
744     }
745
746     private boolean isJobReady(final V1Job job) {
747         if (job.getStatus() != null && job.getStatus().getConditions() != null) {
748             logger.debug("Received Job with conditions ..");
749             for (final V1JobCondition condition : job.getStatus().getConditions()) {
750                 if (JOB_COMPLETE.equalsIgnoreCase(condition.getType())
751                         && TRUE_STRING.equalsIgnoreCase(condition.getStatus())) {
752                     logger.debug("Job completed successfully ...");
753                     return true;
754                 }
755                 if (JOB_FAILED.equalsIgnoreCase(condition.getType())
756                         && TRUE_STRING.equalsIgnoreCase(condition.getStatus())) {
757                     final String message = "Job failed with reason: " + condition.getReason();
758                     logger.error(message);
759                     throw new KubernetesRequestProcessingException(message);
760
761                 }
762             }
763         }
764
765         logger.debug("Job is not ready ...");
766         return false;
767     }
768
769     private boolean isPodReady(final V1Pod pod) {
770         final Optional<V1PodCondition> optional = getPodReadyCondition(pod);
771         if (optional.isPresent()) {
772             final V1PodCondition condition = optional.get();
773             if (TRUE_STRING.equalsIgnoreCase(condition.getStatus())) {
774                 logger.debug("Pod is Ready...");
775                 return true;
776             }
777
778         }
779
780         logger.debug("Pod is not ready ...");
781         return false;
782     }
783
784     private boolean isServiceReady(final V1Service service) {
785         logger.debug("V1Service is Ready ...");
786         return true;
787     }
788
789     private boolean isDeploymentReady(final V1Deployment deployment) {
790         final V1DeploymentSpec spec = deployment.getSpec();
791         final V1DeploymentStatus status = deployment.getStatus();
792
793         if (status != null && status.getReplicas() != null && status.getAvailableReplicas() != null && spec != null
794                 && spec.getReplicas() != null) {
795             if (spec.getReplicas().intValue() == status.getReplicas().intValue()
796                     && status.getAvailableReplicas().intValue() <= spec.getReplicas().intValue()) {
797                 logger.debug("Deployment is Ready...");
798                 return true;
799             }
800         }
801
802         logger.debug("Deployment is not ready ...");
803         return false;
804     }
805
806     private boolean isReplicaSetReady(final V1ReplicaSet replicaSet) {
807         final V1ReplicaSetSpec spec = replicaSet.getSpec();
808         final V1ReplicaSetStatus status = replicaSet.getStatus();
809
810         if (status != null && status.getReadyReplicas() != null && spec != null && spec.getReplicas() != null) {
811             if (spec.getReplicas().intValue() == status.getReadyReplicas().intValue()) {
812                 logger.debug("ReplicaSet is Ready...");
813                 return true;
814             }
815         }
816
817         logger.debug("ReplicaSet is not ready ...");
818         return false;
819     }
820
821     private boolean isDaemonSetReady(final V1DaemonSet daemonSet) {
822
823         final V1DaemonSetSpec spec = daemonSet.getSpec();
824         final V1DaemonSetStatus status = daemonSet.getStatus();
825
826         if (status == null || spec == null) {
827             logger.debug("Found null status/spec \n DaemonSet: {}", daemonSet);
828             return false;
829         }
830
831         if (spec.getUpdateStrategy() != null && spec.getUpdateStrategy().getType() != null) {
832             if (!ROLLING_UPDATE.equalsIgnoreCase(spec.getUpdateStrategy().getType())) {
833                 logger.debug("Type is {} returning true", spec.getUpdateStrategy().getType());
834                 return true;
835             }
836         }
837
838         if (status.getDesiredNumberScheduled() != null && status.getUpdatedNumberScheduled() != null) {
839             if (status.getUpdatedNumberScheduled().intValue() != status.getDesiredNumberScheduled().intValue()) {
840                 logger.debug("DaemonSet is not ready {} out of {} expected pods have been scheduled",
841                         status.getUpdatedNumberScheduled(), status.getDesiredNumberScheduled());
842                 return false;
843             }
844
845             if (spec.getUpdateStrategy() != null && spec.getUpdateStrategy().getRollingUpdate() != null
846                     && status.getNumberReady() != null) {
847
848                 final Integer maxUnavailable =
849                         getMaxUnavailable(spec.getUpdateStrategy().getRollingUpdate().getMaxUnavailable(),
850                                 status.getDesiredNumberScheduled());
851
852                 final int expectedReady = status.getDesiredNumberScheduled().intValue() - maxUnavailable.intValue();
853                 final int numberReady = status.getNumberReady().intValue();
854                 if (!(numberReady >= expectedReady)) {
855                     logger.debug("DaemonSet is not ready {} out of {} expected pods are ready", numberReady,
856                             expectedReady);
857                     return false;
858                 }
859                 logger.debug("DaemonSet is ready {} out of {} expected pods are ready", numberReady, expectedReady);
860                 logger.debug("DaemonSet is Ready...");
861                 return true;
862             }
863
864         }
865
866         logger.debug("DaemonSet is not ready ...");
867         return false;
868     }
869
870     private boolean isStatefulSetReady(final V1StatefulSet statefulSet) {
871         final V1StatefulSetSpec spec = statefulSet.getSpec();
872         final V1StatefulSetStatus status = statefulSet.getStatus();
873
874         if (status == null || spec == null) {
875             logger.debug("Found null status/spec \n StatefulSet: {}", statefulSet);
876             return false;
877         }
878
879         final V1StatefulSetUpdateStrategy updateStrategy = spec.getUpdateStrategy();
880         if (updateStrategy != null && updateStrategy.getType() != null) {
881             if (!ROLLING_UPDATE.equalsIgnoreCase(updateStrategy.getType())) {
882                 logger.debug("Type is {} returning true", updateStrategy.getType());
883                 return true;
884             }
885
886             // Dereference all the pointers because StatefulSets like them
887             int partition = 0;
888             // 1 is the default for replicas if not set
889             int replicas = 1;
890             final V1RollingUpdateStatefulSetStrategy rollingUpdate = updateStrategy.getRollingUpdate();
891             if (rollingUpdate != null && rollingUpdate.getPartition() != null) {
892                 partition = updateStrategy.getRollingUpdate().getPartition().intValue();
893             }
894
895             if (spec.getReplicas() != null) {
896                 replicas = spec.getReplicas().intValue();
897             }
898
899             final int expectedReplicas = replicas - partition;
900
901             if (status.getUpdatedReplicas() != null && status.getUpdatedReplicas().intValue() < expectedReplicas) {
902                 logger.debug("StatefulSet is not ready. {} out of {} expected pods have been scheduled",
903                         status.getUpdatedReplicas(), expectedReplicas);
904                 return false;
905             }
906
907             if (status.getReadyReplicas() != null && status.getReadyReplicas().intValue() != replicas) {
908                 logger.debug("StatefulSet is not ready. {} out of {} expected pods are ready",
909                         status.getReadyReplicas(), replicas);
910                 return false;
911             }
912
913             logger.debug("{} out of {} expected pods are ready", status.getReadyReplicas(), replicas);
914             logger.debug("StatefulSet is Ready...");
915             return true;
916         }
917
918         logger.debug("StatefulSet is not ready ...");
919         return false;
920     }
921
922     private Integer getMaxUnavailable(final IntOrString maxUnavailable, final Integer desiredNumberScheduled) {
923         if (maxUnavailable == null) {
924             logger.debug("maxUnavailable value is {}", maxUnavailable);
925             return desiredNumberScheduled;
926         }
927
928         if (maxUnavailable.isInteger()) {
929             logger.debug("maxUnavailable is Integer: {}", maxUnavailable);
930             return maxUnavailable.getIntValue();
931         }
932
933         if (!maxUnavailable.isInteger()) {
934             final Integer maxUnavailableIntValue = getIntegerValue(maxUnavailable);
935             if (maxUnavailableIntValue != null) {
936                 return (maxUnavailableIntValue.intValue() * desiredNumberScheduled.intValue()) / 100;
937             }
938
939             logger.debug("maxUnavailableIntValue is null {}", maxUnavailableIntValue);
940         }
941         logger.debug("Returning desiredNumberScheduled: {}", desiredNumberScheduled);
942         return desiredNumberScheduled;
943     }
944
945     private Integer getIntegerValue(final IntOrString maxUnavailable) {
946         try {
947             final String strValue = maxUnavailable.getStrValue();
948             if (strValue != null && strValue.length() > 1) {
949                 if (strValue.contains("%")) {
950                     final String val = strValue.trim().replace("%", "");
951                     return Integer.valueOf(val);
952                 }
953                 logger.debug("maxUnavailable is not a percentage");
954             }
955         } catch (final Exception exception) {
956             logger.error("Unable to parse maxUnavailable value: {}", maxUnavailable);
957         }
958         return null;
959     }
960
961     private void closeWatchSilently(final Watch<?> watch) {
962         try {
963             watch.close();
964         } catch (final IOException exception) {
965             logger.warn("Unexpected IOException while closing watch suppressing exception");
966         }
967     }
968
969     private void handleRuntimeException(final String resourceType, final String labelSelector,
970             final RuntimeException runtimeException) {
971         if (runtimeException.getCause() instanceof SocketTimeoutException) {
972             final Throwable cause = runtimeException.getCause();
973             final String message = "Unexpected SocketTimeoutException occurred while getting " + resourceType
974                     + " status using labelSelector: " + labelSelector + " message: " + cause.getMessage();
975             logger.error(message, cause);
976             throw new KubernetesRequestTimeOut(message, cause);
977         }
978         final String message = "Unexpected RuntimeException occurred while getting " + resourceType
979                 + " status using labelSelector: " + labelSelector;
980         logger.error(message, runtimeException);
981         throw new KubernetesRequestProcessingException(message, runtimeException);
982     }
983
984     private void handleApiException(final String resourceType, final String labelSelector,
985             final ApiException exception) {
986         final String message = "Unexpected ApiException occurred while getting " + resourceType
987                 + " status using labelSelector: " + labelSelector + " \n response code: " + exception.getCode()
988                 + " \n response body: " + exception.getResponseBody();
989         logger.error(message, exception);
990         throw new KubernetesRequestProcessingException(message, exception);
991     }
992
993     private void handleIllegalArgumentException(final String resourceType, final String labelSelector,
994             final IllegalArgumentException illegalArgumentException) {
995         final String message = "Unexpected IllegalArgumentException occurred while getting " + resourceType
996                 + " resource using labelSelector: " + labelSelector;
997         logger.error(message, illegalArgumentException);
998         throw new KubernetesRequestProcessingException(message, illegalArgumentException);
999     }
1000
1001     private Optional<V1PodCondition> getPodReadyCondition(final V1Pod pod) {
1002         if (pod.getStatus() != null && pod.getStatus().getConditions() != null) {
1003             final List<V1PodCondition> conditions = pod.getStatus().getConditions();
1004             return conditions.stream().filter(condition -> POD_READY.equals(condition.getType()))
1005                     .peek(condition -> logger.debug("Found {}", condition)).findFirst();
1006
1007         }
1008         logger.warn("Unable to find a {} condition {}", POD_READY, pod.getStatus());
1009         return Optional.empty();
1010     }
1011
1012     /**
1013      * Capturing resource events and objects
1014      * 
1015      * @param <T>
1016      * @param apiClient
1017      * @param call
1018      * @param type
1019      * @return
1020      * @throws ApiException
1021      */
1022     private <T> Map<T, String> getReadyResources(final ApiClient apiClient, final Call call, final Type type)
1023             throws ApiException {
1024         final Watch<T> watch = Watch.createWatch(apiClient, call, type);
1025         logger.debug("Listening for {} events ....", type.getTypeName());
1026
1027         final Map<T, String> resources = new HashMap<>();
1028         try {
1029             while (watch.hasNext()) {
1030                 final Response<T> next = watch.next();
1031                 final T object = next.object;
1032                 logger.debug("Received object: {}", object);
1033                 final String event = next.type;
1034                 logger.debug("Received Event: {}", event);
1035                 resources.put(object, event);
1036             }
1037
1038         } finally {
1039             closeWatchSilently(watch);
1040         }
1041         logger.debug("Finished Listening for {} events ....", type.getTypeName());
1042         return resources;
1043
1044     }
1045
1046     private static <T> boolean isResourceReady(final T object, final String eventType, final Predicate<T> predicate) {
1047
1048         switch (eventType) {
1049             case EVENT_TYPE_ADDED:
1050             case EVENT_TYPE_MODIFIED:
1051                 return predicate.test(object);
1052             case EVENT_TYPE_DELETED:
1053                 logger.debug("{} event received marking it as successfully", EVENT_TYPE_DELETED);
1054                 return true;
1055             case EVENT_TYPE_ERROR:
1056                 final String message = "Error event received for " + (object != null ? object.getClass() : "null");
1057                 logger.error(message);
1058                 logger.debug("{} received: {}", (object != null ? object.getClass() : "null"), object);
1059                 throw new KubernetesRequestProcessingException(message);
1060
1061             default:
1062                 logger.warn("Unhandled event received ... ");
1063                 return false;
1064         }
1065
1066     }
1067 }