All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.hyscale.deployer.services.handler.impl.V1beta2StatefulSetHandler Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2019 Pramati Prism, Inc.
 *
 * 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.
 */
/**
 *
 */
package io.hyscale.deployer.services.handler.impl;

import java.util.List;

import io.hyscale.deployer.services.constants.DeployerConstants;
import io.hyscale.deployer.services.exception.DeployerErrorCodes;
import io.hyscale.deployer.services.handler.ResourceHandlers;
import io.hyscale.deployer.services.handler.ResourceLifeCycleHandler;
import io.hyscale.deployer.services.model.DeployerActivity;
import io.hyscale.deployer.services.model.PodCondition;
import io.hyscale.deployer.services.model.ResourceStatus;
import io.hyscale.deployer.services.util.ExceptionHelper;
import io.hyscale.deployer.services.util.K8sPodUtil;
import io.hyscale.deployer.services.util.K8sResourcePatchUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Lists;
import com.google.gson.JsonSyntaxException;

import io.hyscale.commons.constants.K8SRuntimeConstants;
import io.hyscale.commons.exception.HyscaleException;
import io.hyscale.commons.logger.ActivityContext;
import io.hyscale.commons.logger.WorkflowLogger;
import io.hyscale.commons.models.AnnotationKey;
import io.hyscale.commons.models.ResourceLabelKey;
import io.hyscale.commons.models.Status;
import io.hyscale.commons.utils.GsonProviderUtil;
import io.hyscale.commons.utils.ResourceSelectorUtil;
import io.hyscale.deployer.core.model.ResourceKind;
import io.hyscale.deployer.core.model.ResourceOperation;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.apis.AppsV1beta2Api;
import io.kubernetes.client.openapi.models.V1DeleteOptions;
import io.kubernetes.client.openapi.models.V1Pod;
import io.kubernetes.client.openapi.models.V1beta2StatefulSet;
import io.kubernetes.client.openapi.models.V1beta2StatefulSetList;
import io.kubernetes.client.openapi.models.V1beta2StatefulSetStatus;
import io.kubernetes.client.custom.V1Patch;

/**
 * @author tushart
 *
 */
public class V1beta2StatefulSetHandler implements ResourceLifeCycleHandler {

	private static final Logger LOGGER = LoggerFactory.getLogger(V1beta2StatefulSetHandler.class);

	@Override
	public V1beta2StatefulSet create(ApiClient apiClient, V1beta2StatefulSet resource, String namespace)
			throws HyscaleException {
		if (resource == null) {
			LOGGER.debug("Cannot create null StatefulSet");
			return resource;
		}
		WorkflowLogger.startActivity(DeployerActivity.DEPLOYING_STATEFULSET);
		AppsV1beta2Api appsV1beta2Api = new AppsV1beta2Api(apiClient);
		String name = resource.getMetadata().getName();
		V1beta2StatefulSet statefulSet = null;
		try {
			resource.getMetadata().putAnnotationsItem(
					AnnotationKey.K8S_HYSCALE_LAST_APPLIED_CONFIGURATION.getAnnotation(), GsonProviderUtil.getPrettyGsonBuilder().toJson(resource));
			statefulSet = appsV1beta2Api.createNamespacedStatefulSet(namespace, resource, DeployerConstants.TRUE, null, null);
		} catch (ApiException e) {
			HyscaleException ex = new HyscaleException(e, DeployerErrorCodes.FAILED_TO_CREATE_RESOURCE,
					ExceptionHelper.getExceptionMessage(getKind(), e, ResourceOperation.CREATE));
			LOGGER.error("Error while creating statefulset {} in namespace {}, error {}", name, namespace,
					ex.toString());
			WorkflowLogger.endActivity(Status.FAILED);
			throw ex;
		}
		WorkflowLogger.endActivity(Status.DONE);
		return statefulSet;
	}

	@Override
	public boolean update(ApiClient apiClient, V1beta2StatefulSet resource, String namespace) throws HyscaleException {
		if (resource == null) {
			LOGGER.debug("Cannot update null StatefulSet");
			return false;
		}
		AppsV1beta2Api appsV1beta2Api = new AppsV1beta2Api(apiClient);
		String name = resource.getMetadata().getName();
		V1beta2StatefulSet existingStatefulSet = null;
		try {
			existingStatefulSet = get(apiClient, name, namespace);
		} catch (HyscaleException ex) {
			LOGGER.debug("Error while getting StatefulSet {} in namespace {} for Update, creating new", name,
					namespace);
			V1beta2StatefulSet statefulSet = create(apiClient, resource, namespace);
			return statefulSet != null;
		}

		WorkflowLogger.startActivity(DeployerActivity.DEPLOYING_STATEFULSET);
		try {
			String resourceVersion = existingStatefulSet.getMetadata().getResourceVersion();
			resource.getMetadata().setResourceVersion(resourceVersion);
			appsV1beta2Api.replaceNamespacedStatefulSet(name, namespace, resource, DeployerConstants.TRUE, null, null);
		} catch (ApiException e) {
			HyscaleException ex = new HyscaleException(e, DeployerErrorCodes.FAILED_TO_UPDATE_RESOURCE,
					ExceptionHelper.getExceptionMessage(getKind(), e, ResourceOperation.UPDATE));
			LOGGER.error("Error while updating SatefulSet {} in namespace {}, error {}", name, namespace,
					ex.toString());
			WorkflowLogger.endActivity(Status.FAILED);
			throw ex;
		}
		WorkflowLogger.endActivity(Status.DONE);
		return true;
	}

	@Override
	public V1beta2StatefulSet get(ApiClient apiClient, String name, String namespace) throws HyscaleException {
		AppsV1beta2Api appsV1beta2Api = new AppsV1beta2Api(apiClient);
		V1beta2StatefulSet v1StatefulSet = null;
		try {
			v1StatefulSet = appsV1beta2Api.readNamespacedStatefulSet(name, namespace, DeployerConstants.TRUE, null, null);
		} catch (ApiException e) {
			HyscaleException ex = ExceptionHelper.buildGetException(getKind(), e, ResourceOperation.GET);
			LOGGER.error("Error while fetching StatefulSet {} in namespace {}, error {}", name, namespace,
					ex.toString());
			throw ex;
		}
		return v1StatefulSet;
	}

	@Override
	public List getBySelector(ApiClient apiClient, String selector, boolean label, String namespace)
			throws HyscaleException {
		AppsV1beta2Api appsV1beta2Api = new AppsV1beta2Api(apiClient);
		String labelSelector = label ? selector : null;
		String fieldSelector = label ? null : selector;
		List statefulSets = null;
		try {
			V1beta2StatefulSetList statefulSetList = appsV1beta2Api.listNamespacedStatefulSet(namespace, DeployerConstants.TRUE, null, 
					null, fieldSelector, labelSelector, null, null, null, null);
			statefulSets = statefulSetList != null ? statefulSetList.getItems() : null;
		} catch (ApiException e) {
			HyscaleException ex = ExceptionHelper.buildGetException(getKind(), e, ResourceOperation.GET_BY_SELECTOR);
			LOGGER.error("Error while listing StatefulSets in namespace {}, with selectors {} , error {}", namespace,
					selector, ex.toString());
			throw ex;
		}
		return statefulSets;
	}

	/**
	 * Also deletes previous pods if they are in error state
	 */
	@Override
	public boolean patch(ApiClient apiClient, String name, String namespace, V1beta2StatefulSet target)
			throws HyscaleException {
		if (target == null) {
			LOGGER.debug("Cannot patch null StatefulSet");
			return false;
		}
		AppsV1beta2Api appsV1beta2Api = new AppsV1beta2Api(apiClient);
		target.getMetadata().putAnnotationsItem(AnnotationKey.K8S_HYSCALE_LAST_APPLIED_CONFIGURATION.getAnnotation(),
				GsonProviderUtil.getPrettyGsonBuilder().toJson(target));
		V1beta2StatefulSet sourceStatefulSet = null;
		try {
			sourceStatefulSet = get(apiClient, name, namespace);
		} catch (HyscaleException e) {
			LOGGER.debug("Error while getting StatefulSet {} in namespace {} for Patch, creating new", name, namespace);
			V1beta2StatefulSet statefulSet = create(apiClient, target, namespace);
			return statefulSet != null;
		}
		WorkflowLogger.startActivity(DeployerActivity.DEPLOYING_STATEFULSET);
		Object patchObject = null;
		String lastAppliedConfig = sourceStatefulSet.getMetadata().getAnnotations()
				.get(AnnotationKey.K8S_HYSCALE_LAST_APPLIED_CONFIGURATION.getAnnotation());
		boolean deleteRequired = false;
		String serviceName = sourceStatefulSet.getMetadata().getLabels().get(ResourceLabelKey.SERVICE_NAME.getLabel());
		try {
			patchObject = K8sResourcePatchUtil.getJsonPatch(GsonProviderUtil.getPrettyGsonBuilder().fromJson(lastAppliedConfig, V1beta2StatefulSet.class),
					target, V1beta2StatefulSet.class);
			deleteRequired = isDeletePodRequired(apiClient, serviceName, namespace);
			LOGGER.debug("Deleting existing pods for updating StatefulSet patch required :{}", deleteRequired);
			V1Patch v1Patch = new V1Patch(patchObject.toString());
			appsV1beta2Api.patchNamespacedStatefulSet(name, namespace, v1Patch, DeployerConstants.TRUE, null, null, null);
		} catch (HyscaleException ex) {
			LOGGER.error("Error while creating patch for StatefulSet {}, source {}, target {}, error {}", name,
					sourceStatefulSet, target, ex.toString());
			WorkflowLogger.endActivity(Status.FAILED);
			throw ex;
		} catch (ApiException e) {
			HyscaleException ex = new HyscaleException(e, DeployerErrorCodes.FAILED_TO_PATCH_RESOURCE,
					ExceptionHelper.getExceptionMessage(getKind(), e, ResourceOperation.PATCH));
			LOGGER.error("Error while patching StatefulSet {} in namespace {} , error {}", name, namespace,
					ex.toString());
			WorkflowLogger.endActivity(Status.FAILED);
			throw ex;
		} finally {
			if (deleteRequired) {
				V1PodHandler podHandler = (V1PodHandler) ResourceHandlers.getHandlerOf(ResourceKind.POD.getKind());
				podHandler.deleteBySelector(apiClient, getPodSelector(serviceName), true, namespace, false);
			}
		}
		WorkflowLogger.endActivity(Status.DONE);
		return true;
	}

	/**
	 * 
	 * @param apiClient
	 * @param name
	 * @param namespace
	 * @return true if pods needs to be deleted
	 * @throws HyscaleException
	 */
	private boolean isDeletePodRequired(ApiClient apiClient, String name, String namespace) throws HyscaleException {
		V1PodHandler podHandler = (V1PodHandler) ResourceHandlers.getHandlerOf(ResourceKind.POD.getKind());
		List v1PodList = podHandler.getBySelector(apiClient, getPodSelector(name), true, namespace);
		boolean isPodInErrorState = false;
		if (v1PodList != null && !v1PodList.isEmpty()) {
            isPodInErrorState = v1PodList.stream()
                    .anyMatch(each -> !K8sPodUtil.getAggregatedStatusOfContainersForPod(each)
                            .equalsIgnoreCase(K8SRuntimeConstants.POD_RUNING_STATE_CONDITION)
                            || !K8sPodUtil.checkForPodCondition(each, PodCondition.READY));
		}
		return isPodInErrorState;
	}

	@Override
	public boolean delete(ApiClient apiClient, String name, String namespace, boolean wait) throws HyscaleException {
		ActivityContext activityContext = new ActivityContext(DeployerActivity.DELETING_STATEFULSET);
		WorkflowLogger.startActivity(activityContext);
		try {
			delete(apiClient, name, namespace);
			if (wait) {
			    List pendingStatefulSets = Lists.newArrayList();
			    pendingStatefulSets.add(name);
			    waitForResourceDeletion(apiClient, pendingStatefulSets, namespace, activityContext);
			}
		} catch (ApiException e) {
			if (e.getCode() == 404) {
				WorkflowLogger.endActivity(activityContext, Status.NOT_FOUND);
				return false;
			}
			HyscaleException ex = new HyscaleException(e, DeployerErrorCodes.FAILED_TO_DELETE_RESOURCE,
					ExceptionHelper.getExceptionMessage(getKind(), e, ResourceOperation.DELETE));
			LOGGER.error("Error while deleting StatefulSet {} in namespace {} , error {}", name, namespace,
					ex.toString());
			WorkflowLogger.endActivity(activityContext, Status.FAILED);
			throw ex;
		}
		LOGGER.debug("Deleting StatefulSet done");
		WorkflowLogger.endActivity(activityContext, Status.DONE);
		return true;
	}

    private void delete(ApiClient apiClient, String name, String namespace) throws ApiException {
        AppsV1beta2Api appsV1beta2Api = new AppsV1beta2Api(apiClient);
        V1DeleteOptions deleteOptions = getDeleteOptions();
        deleteOptions.setApiVersion("apps/v1beta2");
        try {
            appsV1beta2Api.deleteNamespacedStatefulSet(name, namespace, DeployerConstants.TRUE, null, null, null, null,
                    deleteOptions);
        } catch (JsonSyntaxException e) {
            // K8s end exception ignore
        }
    }
	

	@Override
	public boolean deleteBySelector(ApiClient apiClient, String selector, boolean label, String namespace, boolean wait)
			throws HyscaleException {

		try {
			List statefulSets = getBySelector(apiClient, selector, label, namespace);

			if (statefulSets == null || statefulSets.isEmpty()) {
				return false;
			}
			for (V1beta2StatefulSet statefulSet : statefulSets) {
				delete(apiClient, statefulSet.getMetadata().getName(), namespace, wait);
			}
		} catch (HyscaleException e) {
			if (DeployerErrorCodes.RESOURCE_NOT_FOUND.equals(e.getHyscaleError())) {
				return false;
			}
			throw e;
		}
		return true;

	}

	@Override
	public String getKind() {
		return ResourceKind.STATEFUL_SET.getKind();
	}

	private String getPodSelector(String serviceName) {
		return ResourceSelectorUtil.getServiceSelector(null, serviceName);
	}

	@Override
	public boolean cleanUp() {
		return true;
	}

	@Override
	public ResourceStatus status(V1beta2StatefulSet statefulSet) {
		if (statefulSet.getStatus() == null) {
			return ResourceStatus.FAILED;
		} else {
			V1beta2StatefulSetStatus stsStatus = statefulSet.getStatus();
			String currentRevision = stsStatus.getCurrentRevision();
			String updateRevision = stsStatus.getUpdateRevision();
			// stsStatus.getConditions()
			Integer currentReplicas = stsStatus.getCurrentReplicas();
			Integer readyReplicas = stsStatus.getReadyReplicas();
			Integer intendedReplicas = statefulSet.getSpec().getReplicas();
			// Success case update remaining pods status and return
			if (updateRevision != null && updateRevision.equals(currentRevision) && intendedReplicas != null
					&& intendedReplicas.equals(currentReplicas) && intendedReplicas.equals(readyReplicas)) {
				return ResourceStatus.STABLE;
			}
			return ResourceStatus.PENDING;
		}
	}

	@Override
	public int getWeight() {
	    return ResourceKind.STATEFUL_SET.getWeight();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy