diff --git a/src/main/java/com/autotune/analyzer/exceptions/ApplyRecommendationsError.java b/src/main/java/com/autotune/analyzer/exceptions/ApplyRecommendationsError.java new file mode 100644 index 000000000..45fa5df64 --- /dev/null +++ b/src/main/java/com/autotune/analyzer/exceptions/ApplyRecommendationsError.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, IBM Corporation and others. + * + * 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 com.autotune.analyzer.exceptions; + +public class ApplyRecommendationsError extends Exception { + public ApplyRecommendationsError() { + } + + public ApplyRecommendationsError(String message) { + super(message); + } +} diff --git a/src/main/java/com/autotune/analyzer/exceptions/InvalidRecommendationUpdaterType.java b/src/main/java/com/autotune/analyzer/exceptions/InvalidRecommendationUpdaterType.java new file mode 100644 index 000000000..67ed3ed78 --- /dev/null +++ b/src/main/java/com/autotune/analyzer/exceptions/InvalidRecommendationUpdaterType.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, IBM Corporation and others. + * + * 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 com.autotune.analyzer.exceptions; + +public class InvalidRecommendationUpdaterType extends Exception { + public InvalidRecommendationUpdaterType() { + } + + public InvalidRecommendationUpdaterType(String message) { + super(message); + } +} diff --git a/src/main/java/com/autotune/analyzer/exceptions/UnableToCreateVPAException.java b/src/main/java/com/autotune/analyzer/exceptions/UnableToCreateVPAException.java new file mode 100644 index 000000000..3b5c3c494 --- /dev/null +++ b/src/main/java/com/autotune/analyzer/exceptions/UnableToCreateVPAException.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, IBM Corporation and others. + * + * 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 com.autotune.analyzer.exceptions; + +public class UnableToCreateVPAException extends Exception { + public UnableToCreateVPAException() { + } + + public UnableToCreateVPAException(String message) { + super(message); + } +} + diff --git a/src/main/java/com/autotune/analyzer/recommendations/updater/RecommendationUpdater.java b/src/main/java/com/autotune/analyzer/recommendations/updater/RecommendationUpdater.java new file mode 100644 index 000000000..2ddd75cee --- /dev/null +++ b/src/main/java/com/autotune/analyzer/recommendations/updater/RecommendationUpdater.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, IBM Corporation and others. + * + * 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 com.autotune.analyzer.recommendations.updater; + +import com.autotune.analyzer.exceptions.ApplyRecommendationsError; +import com.autotune.analyzer.exceptions.InvalidRecommendationUpdaterType; +import com.autotune.analyzer.kruizeObject.KruizeObject; + +/** + * This interface defines the abstraction for updating resource recommendations in a system. + * Implementing classes will provide the logic to update resources with recommendations for a specific resources, + * such as CPU, memory, or any other resources that require periodic or dynamic adjustments. + * + * The RecommendationUpdater interface is designed to be extended by different updater classes. + * For example, vpaUpdaterImpl for updating resources with recommendations related to CPU and memory resources. + */ + +public interface RecommendationUpdater { + /** + * Retrieves an instance of a specific updater implementation based on the provided updater type + * + * @param updaterType String the type of updater to retrieve + * @return RecommendationUpdaterImpl An instance of provided updater type class + * @throws InvalidRecommendationUpdaterType If the provided updater type doesn't match any valid type of updater. + */ + RecommendationUpdaterImpl getUpdaterInstance(String updaterType) throws InvalidRecommendationUpdaterType; + + /** + * Checks whether the necessary updater dependencies are installed or available in the system. + * + * @return boolean true if the required updaters are installed, false otherwise. + */ + boolean isUpdaterInstalled(); + + /** + * Generates resource recommendations for a specific experiment based on the experiment's name. + * + * @param experimentName String The name of the experiment for which the resource recommendations should be generated. + * @return KruizeObject containing recommendations + */ + KruizeObject generateResourceRecommendationsForExperiment(String experimentName); + + /** + * Applies the resource recommendations contained within the provided KruizeObject + * This method will take the KruizeObject, which contains the resource recommendations, + * and apply them to the desired resources. + * + * @param kruizeObject KruizeObject containing the resource recommendations to be applied. + * @throws ApplyRecommendationsError in case of any error. + */ + void applyResourceRecommendationsForExperiment(KruizeObject kruizeObject) throws ApplyRecommendationsError; +} diff --git a/src/main/java/com/autotune/analyzer/recommendations/updater/RecommendationUpdaterImpl.java b/src/main/java/com/autotune/analyzer/recommendations/updater/RecommendationUpdaterImpl.java new file mode 100644 index 000000000..f423dcd3c --- /dev/null +++ b/src/main/java/com/autotune/analyzer/recommendations/updater/RecommendationUpdaterImpl.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, IBM Corporation and others. + * + * 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 com.autotune.analyzer.recommendations.updater; + +import com.autotune.analyzer.exceptions.ApplyRecommendationsError; +import com.autotune.analyzer.exceptions.FetchMetricsError; +import com.autotune.analyzer.exceptions.InvalidRecommendationUpdaterType; +import com.autotune.analyzer.kruizeObject.KruizeObject; +import com.autotune.analyzer.recommendations.engine.RecommendationEngine; +import com.autotune.analyzer.recommendations.updater.vpa.VpaUpdaterImpl; +import com.autotune.analyzer.utils.AnalyzerConstants; +import com.autotune.analyzer.utils.AnalyzerErrorConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RecommendationUpdaterImpl implements RecommendationUpdater { + + private static final Logger LOGGER = LoggerFactory.getLogger(RecommendationUpdaterImpl.class); + + /** + * Retrieves an instance of a specific updater implementation based on the provided updater type + * + * @param updaterType String the type of updater to retrieve + * @return RecommendationUpdaterImpl An instance of provided updater type class + * @throws InvalidRecommendationUpdaterType If the provided updater type doesn't match any valid type of updater. + */ + @Override + public RecommendationUpdaterImpl getUpdaterInstance(String updaterType) throws InvalidRecommendationUpdaterType { + if (AnalyzerConstants.RecommendationUpdaterConstants.SupportedUpdaters.VPA.equalsIgnoreCase(updaterType)) { + return VpaUpdaterImpl.getInstance(); + } else { + throw new InvalidRecommendationUpdaterType(String.format(AnalyzerErrorConstants.RecommendationUpdaterErrors.UNSUPPORTED_UPDATER_TYPE, updaterType)); + } + } + + /** + * Checks whether the necessary updater dependencies are installed or available in the system. + * @return boolean true if the required updaters are installed, false otherwise. + */ + @Override + public boolean isUpdaterInstalled() { + /* + * This function will be implemented by specific updater type child classes + */ + return false; + } + + /** + * Generates resource recommendations for a specific experiment based on the experiment's name. + * + * @param experimentName String The name of the experiment for which the resource recommendations should be generated. + * @return KruizeObject containing recommendations + */ + @Override + public KruizeObject generateResourceRecommendationsForExperiment(String experimentName) { + try { + LOGGER.debug(AnalyzerConstants.RecommendationUpdaterConstants.InfoMsgs.GENERATING_RECOMMENDATIONS, experimentName); + // generating latest recommendations for experiment + RecommendationEngine recommendationEngine = new RecommendationEngine(experimentName, null, null); + int calCount = 0; + String validationMessage = recommendationEngine.validate_local(); + if (validationMessage.isEmpty()) { + KruizeObject kruizeObject = recommendationEngine.prepareRecommendations(calCount, null); + if (kruizeObject.getValidation_data().isSuccess()) { + LOGGER.debug(AnalyzerConstants.RecommendationUpdaterConstants.InfoMsgs.GENERATED_RECOMMENDATIONS, experimentName); + return kruizeObject; + } else { + throw new Exception(kruizeObject.getValidation_data().getMessage()); + } + } else { + throw new Exception(validationMessage); + } + } catch (Exception | FetchMetricsError e) { + LOGGER.error(AnalyzerErrorConstants.RecommendationUpdaterErrors.GENERATE_RECOMMNEDATION_FAILED, experimentName); + LOGGER.debug(e.getMessage()); + return null; + } + } + + /** + * Applies the resource recommendations contained within the provided KruizeObject + * This method will take the KruizeObject, which contains the resource recommendations, + * and apply them to the desired resources. + * + * @param kruizeObject KruizeObject containing the resource recommendations to be applied. + * @throws ApplyRecommendationsError in case of any error. + */ + @Override + public void applyResourceRecommendationsForExperiment(KruizeObject kruizeObject) throws ApplyRecommendationsError { + /* + * This function will be implemented by specific updater type child classes + */ + } +} diff --git a/src/main/java/com/autotune/analyzer/recommendations/updater/vpa/VpaUpdaterImpl.java b/src/main/java/com/autotune/analyzer/recommendations/updater/vpa/VpaUpdaterImpl.java new file mode 100644 index 000000000..40e9ddb75 --- /dev/null +++ b/src/main/java/com/autotune/analyzer/recommendations/updater/vpa/VpaUpdaterImpl.java @@ -0,0 +1,377 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, IBM Corporation and others. + * + * 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 com.autotune.analyzer.recommendations.updater.vpa; + +import com.autotune.analyzer.exceptions.ApplyRecommendationsError; +import com.autotune.analyzer.exceptions.UnableToCreateVPAException; +import com.autotune.analyzer.kruizeObject.KruizeObject; +import com.autotune.analyzer.recommendations.RecommendationConfigItem; +import com.autotune.analyzer.recommendations.updater.RecommendationUpdaterImpl; +import com.autotune.analyzer.recommendations.utils.RecommendationUtils; +import com.autotune.analyzer.utils.AnalyzerConstants; +import com.autotune.analyzer.utils.AnalyzerErrorConstants; +import com.autotune.common.k8sObjects.K8sObject; +import com.autotune.common.data.result.ContainerData; +import com.autotune.analyzer.recommendations.objects.MappedRecommendationForTimestamp; +import com.autotune.analyzer.recommendations.objects.TermRecommendations; +import io.fabric8.autoscaling.api.model.v1.*; +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.api.model.Quantity; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionList; +import io.fabric8.kubernetes.api.model.autoscaling.v1.CrossVersionObjectReferenceBuilder; +import io.fabric8.kubernetes.client.DefaultKubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.dsl.ApiextensionsAPIGroupDSL; +import io.fabric8.verticalpodautoscaler.client.DefaultVerticalPodAutoscalerClient; +import io.fabric8.verticalpodautoscaler.client.NamespacedVerticalPodAutoscalerClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class VpaUpdaterImpl extends RecommendationUpdaterImpl { + private static final Logger LOGGER = LoggerFactory.getLogger(VpaUpdaterImpl.class); + private static VpaUpdaterImpl vpaUpdater; + + private KubernetesClient kubernetesClient; + private ApiextensionsAPIGroupDSL apiextensionsClient; + + + private VpaUpdaterImpl() { + this.kubernetesClient = new DefaultKubernetesClient(); + this.apiextensionsClient = kubernetesClient.apiextensions(); + } + + public static VpaUpdaterImpl getInstance() { + if (null != vpaUpdater) { + return vpaUpdater; + } + + synchronized (VpaUpdaterImpl.class) { + if (null == vpaUpdater) { + vpaUpdater = new VpaUpdaterImpl(); + } + } + + return vpaUpdater; + } + + /** + * Checks whether the necessary updater dependencies are installed or available in the system. + * @return boolean true if the required updaters are installed, false otherwise. + */ + @Override + public boolean isUpdaterInstalled() { + try { + LOGGER.debug(AnalyzerConstants.RecommendationUpdaterConstants.InfoMsgs.CHECKING_IF_UPDATER_INSTALLED, + AnalyzerConstants.RecommendationUpdaterConstants.SupportedUpdaters.VPA); + // checking if VPA CRD is present or not + boolean isVpaInstalled = false; + CustomResourceDefinitionList crdList = apiextensionsClient.v1().customResourceDefinitions().list(); + if (null != crdList && null != crdList.getItems() && !crdList.getItems().isEmpty()) { + isVpaInstalled = crdList.getItems().stream().anyMatch(crd -> AnalyzerConstants.RecommendationUpdaterConstants.VPA.VPA_PLURAL.equalsIgnoreCase(crd.getSpec().getNames().getKind())); + } + if (isVpaInstalled) { + LOGGER.debug(AnalyzerConstants.RecommendationUpdaterConstants.InfoMsgs.FOUND_UPDATER_INSTALLED, AnalyzerConstants.RecommendationUpdaterConstants.SupportedUpdaters.VPA); + } else { + LOGGER.error(AnalyzerErrorConstants.RecommendationUpdaterErrors.UPDATER_NOT_INSTALLED); + } + return isVpaInstalled; + } catch (Exception e) { + LOGGER.error(e.getMessage()); + return false; + } + } + + /** + * Checks if a Vertical Pod Autoscaler (VPA) object with the specified name is present. + * + * @param vpaName String containing the name of the VPA object to search for + * @return true if the VPA object with the specified name is present, false otherwise + */ + private boolean checkIfVpaIsPresent(String vpaName) { + try { + if (null == vpaName || vpaName.isEmpty()) { + throw new Exception(AnalyzerErrorConstants.RecommendationUpdaterErrors.INVALID_VPA_NAME); + } else { + LOGGER.debug(String.format(AnalyzerConstants.RecommendationUpdaterConstants.InfoMsgs.CHECKING_IF_VPA_PRESENT, vpaName)); + NamespacedVerticalPodAutoscalerClient client = new DefaultVerticalPodAutoscalerClient(); + VerticalPodAutoscalerList vpas = client.v1().verticalpodautoscalers().inAnyNamespace().list(); + + if (null != vpas && null != vpas.getItems() && !vpas.getItems().isEmpty()) { + // TODO:// later we can also check here is the recommender is Kruize to confirm + for (VerticalPodAutoscaler vpa : vpas.getItems()) { + if (vpaName.equals(vpa.getMetadata().getName())) { + LOGGER.debug(String.format(AnalyzerConstants.RecommendationUpdaterConstants.InfoMsgs.VPA_WITH_NAME_FOUND, vpaName)); + return true; + } + } + } + LOGGER.error(String.format(AnalyzerConstants.RecommendationUpdaterConstants.InfoMsgs.VPA_WITH_NAME_NOT_FOUND, vpaName)); + return false; + } + } catch (Exception e) { + LOGGER.error("Error while checking VPA presence: " + e.getMessage(), e); + return false; + } + } + + + /** + * Returns the VPA Object if present with the name + * + * @param vpaName String containing the name of the VPA object to search for + * @return VerticalPodAutoscaler if the VPA object with the specified name is present, null otherwise + */ + private VerticalPodAutoscaler getVpaIsPresent(String vpaName) { + try { + if (null == vpaName || vpaName.isEmpty()) { + throw new Exception(AnalyzerErrorConstants.RecommendationUpdaterErrors.INVALID_VPA_NAME); + } else { + LOGGER.debug(String.format(AnalyzerConstants.RecommendationUpdaterConstants.InfoMsgs.CHECKING_IF_VPA_PRESENT, vpaName)); + NamespacedVerticalPodAutoscalerClient client = new DefaultVerticalPodAutoscalerClient(); + VerticalPodAutoscalerList vpas = client.v1().verticalpodautoscalers().inAnyNamespace().list(); + + if (null != vpas && null != vpas.getItems() && !vpas.getItems().isEmpty()) { + for (VerticalPodAutoscaler vpa : vpas.getItems()) { + if (vpaName.equals(vpa.getMetadata().getName())) { + LOGGER.debug(String.format(AnalyzerConstants.RecommendationUpdaterConstants.InfoMsgs.VPA_WITH_NAME_FOUND, vpaName)); + return vpa; + } + } + } + LOGGER.error(String.format(AnalyzerConstants.RecommendationUpdaterConstants.InfoMsgs.VPA_WITH_NAME_NOT_FOUND, vpaName)); + return null; + } + } catch (Exception e) { + LOGGER.error("Error while checking VPA presence: " + e.getMessage(), e); + return null; + } + } + + + /** + * Applies the resource recommendations contained within the provided KruizeObject + * This method will take the KruizeObject, which contains the resource recommendations, + * and apply them to the desired resources. + * + * @param kruizeObject KruizeObject containing the resource recommendations to be applied. + * @throws ApplyRecommendationsError in case of any error. + */ + @Override + public void applyResourceRecommendationsForExperiment(KruizeObject kruizeObject) throws ApplyRecommendationsError { + try { + // checking if VPA is installed or not + if (!isUpdaterInstalled()) { + LOGGER.error(AnalyzerErrorConstants.RecommendationUpdaterErrors.UPDATER_NOT_INSTALLED); + } else { + String expName = kruizeObject.getExperimentName(); + boolean vpaPresent = checkIfVpaIsPresent(expName); + + // create VPA Object is not present + if (!vpaPresent) { + createVpaObject(kruizeObject); + } + + for (K8sObject k8sObject: kruizeObject.getKubernetes_objects()) { + List containerRecommendations = convertRecommendationsToContainerPolicy(k8sObject.getContainerDataMap()); + if (containerRecommendations.isEmpty()){ + LOGGER.error(AnalyzerErrorConstants.RecommendationUpdaterErrors.RECOMMENDATION_DATA_NOT_PRESENT); + } else { + RecommendedPodResources recommendedPodResources = new RecommendedPodResources(); + recommendedPodResources.setContainerRecommendations(containerRecommendations); + VerticalPodAutoscalerStatus vpaObjectStatus = new VerticalPodAutoscalerStatusBuilder() + .withRecommendation(recommendedPodResources) + .build(); + + // patching existing VPA Object + if (vpaObjectStatus != null) { + VerticalPodAutoscaler vpaObject = getVpaIsPresent(expName); + vpaObject.setStatus(vpaObjectStatus); + + NamespacedVerticalPodAutoscalerClient client = new DefaultVerticalPodAutoscalerClient(); + client.v1().verticalpodautoscalers() + .inNamespace(vpaObject + .getMetadata() + .getNamespace()) + .withName(vpaObject + .getMetadata() + .getName()) + .patchStatus(vpaObject); + + LOGGER.debug(String.format(AnalyzerConstants.RecommendationUpdaterConstants.InfoMsgs.VPA_PATCHED, + vpaObject.getMetadata().getName())); + } + } + } + } + } catch (Exception e) { + throw new ApplyRecommendationsError(e.getMessage()); + } + } + + /** + * This function converts container recommendations for VPA Container Recommendations Object Format + */ + private List convertRecommendationsToContainerPolicy(HashMap containerDataMap) { + List containerRecommendations = new ArrayList<>(); + + for (Map.Entry containerDataEntry : containerDataMap.entrySet()) { + // fetching container data + ContainerData containerData = containerDataEntry.getValue(); + String containerName = containerData.getContainer_name(); + HashMap recommendationData = containerData.getContainerRecommendations().getData(); + + // checking if recommendation data is present + if (null == recommendationData) { + LOGGER.error(AnalyzerErrorConstants.RecommendationUpdaterErrors.RECOMMENDATION_DATA_NOT_PRESENT); + } else { + for (MappedRecommendationForTimestamp value : recommendationData.values()) { + /* + * Fetching Short Term Cost Recommendations By Default + * TODO:// Implement functionality to choose the desired term and model + **/ + TermRecommendations termRecommendations = value.getShortTermRecommendations(); + HashMap> recommendationsConfig = termRecommendations.getCostRecommendations().getConfig(); + + Double cpuRecommendationValue = recommendationsConfig.get(AnalyzerConstants.ResourceSetting.requests).get(AnalyzerConstants.RecommendationItem.CPU).getAmount(); + Double memoryRecommendationValue = recommendationsConfig.get(AnalyzerConstants.ResourceSetting.requests).get(AnalyzerConstants.RecommendationItem.MEMORY).getAmount(); + + LOGGER.debug(String.format(AnalyzerConstants.RecommendationUpdaterConstants.InfoMsgs.RECOMMENDATION_VALUE, + AnalyzerConstants.RecommendationItem.CPU, containerName, cpuRecommendationValue)); + LOGGER.debug(String.format(AnalyzerConstants.RecommendationUpdaterConstants.InfoMsgs.RECOMMENDATION_VALUE, + AnalyzerConstants.RecommendationItem.MEMORY, containerName, memoryRecommendationValue)); + + String cpuRecommendationValueForVpa = RecommendationUtils.resource2str(AnalyzerConstants.RecommendationItem.CPU.toString(), + cpuRecommendationValue); + String memoryRecommendationValueForVpa = RecommendationUtils.resource2str(AnalyzerConstants.RecommendationItem.MEMORY.toString(), + memoryRecommendationValue); + + // creating container resource vpa object + RecommendedContainerResources recommendedContainerResources = new RecommendedContainerResources(); + recommendedContainerResources.setContainerName(containerName); + + // setting target values + Map target = new HashMap<>(); + target.put(AnalyzerConstants.RecommendationItem.CPU.toString(), new Quantity(cpuRecommendationValueForVpa)); + target.put(AnalyzerConstants.RecommendationItem.MEMORY.toString(), new Quantity(memoryRecommendationValueForVpa)); + + // setting lower bound values + Map lowerBound = new HashMap<>(); + lowerBound.put(AnalyzerConstants.RecommendationItem.CPU.toString(), new Quantity(cpuRecommendationValueForVpa)); + lowerBound.put(AnalyzerConstants.RecommendationItem.MEMORY.toString(), new Quantity(memoryRecommendationValueForVpa)); + + // setting upper bound values + Map upperBound = new HashMap<>(); + upperBound.put(AnalyzerConstants.RecommendationItem.CPU.toString(), new Quantity(cpuRecommendationValueForVpa)); + upperBound.put(AnalyzerConstants.RecommendationItem.MEMORY.toString(), new Quantity(memoryRecommendationValueForVpa)); + + recommendedContainerResources.setLowerBound(lowerBound); + recommendedContainerResources.setTarget(target); + recommendedContainerResources.setUpperBound(upperBound); + + containerRecommendations.add(recommendedContainerResources); + } + } + } + return containerRecommendations; + } + + /* + * Creates a Vertical Pod Autoscaler (VPA) object in the specified namespace + * for the given deployment and containers. + */ + public void createVpaObject(KruizeObject kruizeObject) throws UnableToCreateVPAException { + try { + // checks if updater is installed or not + if (isUpdaterInstalled()) { + LOGGER.debug(String.format(AnalyzerConstants.RecommendationUpdaterConstants.InfoMsgs.CREATEING_VPA, kruizeObject.getExperimentName())); + + // updating recommender to Kruize for VPA Object + Map additionalVpaObjectProps = getAdditionalVpaObjectProps(); + + // updating controlled resources + List controlledResources = new ArrayList<>(); + controlledResources.add(AnalyzerConstants.RecommendationItem.CPU.toString()); + controlledResources.add(AnalyzerConstants.RecommendationItem.MEMORY.toString()); + + // updating container policies + for (K8sObject k8sObject: kruizeObject.getKubernetes_objects()) { + List containers = new ArrayList<>(k8sObject.getContainerDataMap().keySet()); + List containerPolicies = new ArrayList<>(); + for (String containerName : containers) { + ContainerResourcePolicy policy = new ContainerResourcePolicyBuilder() + .withContainerName(containerName) + .withControlledResources(controlledResources) + .build(); + containerPolicies.add(policy); + } + + PodResourcePolicy podPolicy = new PodResourcePolicyBuilder() + .withContainerPolicies(containerPolicies) + .build(); + + VerticalPodAutoscaler vpa = new VerticalPodAutoscalerBuilder() + .withApiVersion(AnalyzerConstants.RecommendationUpdaterConstants.VPA.VPA_API_VERSION) + .withKind(AnalyzerConstants.RecommendationUpdaterConstants.VPA.VPA_PLURAL) + .withMetadata(new ObjectMeta() {{ + setName(kruizeObject.getExperimentName()); + }}) + .withSpec(new VerticalPodAutoscalerSpecBuilder() + .withTargetRef(new CrossVersionObjectReferenceBuilder() + .withApiVersion(AnalyzerConstants.RecommendationUpdaterConstants.VPA.VPA_TARGET_REF_API_VERSION) + .withKind(AnalyzerConstants.RecommendationUpdaterConstants.VPA.VPA_TARGET_REF_KIND) + .withName(k8sObject.getName()) + .build()) + .withResourcePolicy(podPolicy) + .withAdditionalProperties(additionalVpaObjectProps) + .build()) + .build(); + + kubernetesClient.resource(vpa).inNamespace(k8sObject.getNamespace()).createOrReplace(); + LOGGER.debug(String.format(AnalyzerConstants.RecommendationUpdaterConstants.InfoMsgs.CREATED_VPA, kruizeObject.getExperimentName())); + } + + } else { + throw new UnableToCreateVPAException(AnalyzerErrorConstants.RecommendationUpdaterErrors.UPDATER_NOT_INSTALLED); + } + } catch (Exception e) { + throw new UnableToCreateVPAException(e.getMessage()); + } + } + + /* + * Prepare Object Map with addiional properties required for VPA Object + * such as - Recommender Name + */ + private static Map getAdditionalVpaObjectProps() { + Map additionalVpaObjectProps = new HashMap<>(); + List> recommenders = new ArrayList<>(); + Map recommender = new HashMap<>(); + recommender.put(AnalyzerConstants.RecommendationUpdaterConstants.VPA.RECOMMENDER_KEY, + AnalyzerConstants.RecommendationUpdaterConstants.VPA.RECOMMENDER_NAME); + recommenders.add(recommender); + additionalVpaObjectProps.put(AnalyzerConstants.RecommendationUpdaterConstants.VPA.RECOMMENDERS, recommenders); + return additionalVpaObjectProps; + } +} diff --git a/src/main/java/com/autotune/analyzer/recommendations/utils/RecommendationUtils.java b/src/main/java/com/autotune/analyzer/recommendations/utils/RecommendationUtils.java index ce65ce5aa..8bb996695 100644 --- a/src/main/java/com/autotune/analyzer/recommendations/utils/RecommendationUtils.java +++ b/src/main/java/com/autotune/analyzer/recommendations/utils/RecommendationUtils.java @@ -371,5 +371,30 @@ public static String getSupportedModelBasedOnModelName(String modelName) { return null; } + + /** + * This function converts the cpu and memory values to VPA desired format + */ + public static String resource2str(String resource, double value) { + if (resource.equalsIgnoreCase(AnalyzerConstants.RecommendationItem.CPU.toString())) { + // cpu related conversions + if (value < 1) { + return (int) (value * 1000) + "m"; + } else { + return String.valueOf(value); + } + } else { + // memory related conversions + if (value < 1024) { + return (int) value + "B"; + } else if (value < 1024 * 1024) { + return (int) (value / 1024) + "k"; + } else if (value < 1024 * 1024 * 1024) { + return (int) (value / 1024 / 1024) + "Mi"; + } else { + return (int) (value / 1024 / 1024 / 1024) + "Gi"; + } + } + } } diff --git a/src/main/java/com/autotune/analyzer/utils/AnalyzerConstants.java b/src/main/java/com/autotune/analyzer/utils/AnalyzerConstants.java index 391ddc617..135a5ece3 100644 --- a/src/main/java/com/autotune/analyzer/utils/AnalyzerConstants.java +++ b/src/main/java/com/autotune/analyzer/utils/AnalyzerConstants.java @@ -675,4 +675,50 @@ private APIVersionConstants() { } } } + + public static final class RecommendationUpdaterConstants { + private RecommendationUpdaterConstants() { + + } + + public static final class SupportedUpdaters { + public static final String VPA = "vpa"; + + private SupportedUpdaters() { + + } + } + + public static final class VPA { + public static final String VPA_PLURAL = "VerticalPodAutoscaler"; + public static final String RECOMMENDERS = "recommenders"; + public static final String RECOMMENDER_KEY = "name"; + public static final String RECOMMENDER_NAME = "Kruize"; + public static final String VPA_API_VERSION = "autoscaling.k8s.io/v1"; + public static final String VPA_TARGET_REF_API_VERSION = "apps/v1"; + public static final String VPA_TARGET_REF_KIND = "Deployment"; + + + private VPA() { + + } + } + + public static final class InfoMsgs { + public static final String GENERATING_RECOMMENDATIONS = "Generating recommendations for experiment: {}"; + public static final String GENERATED_RECOMMENDATIONS = "Generated recommendations for experiment: {}"; + public static final String CHECKING_IF_UPDATER_INSTALLED = "Verifying if the updater is installed: {}"; + public static final String FOUND_UPDATER_INSTALLED = "Found updater is installed: {}"; + public static final String CHECKING_IF_VPA_PRESENT = "Checking for the presence of VPA with name: %s"; + public static final String VPA_WITH_NAME_FOUND = "VPA with name %s found."; + public static final String VPA_WITH_NAME_NOT_FOUND = "VPA with name %s not found."; + public static final String RECOMMENDATION_VALUE = "%s request recommendations for container %s is %f"; + public static final String VPA_PATCHED = "VPA object with name %s is patched successfully with recommendations."; + public static final String CREATEING_VPA = "Creating VPA with name: %s"; + public static final String CREATED_VPA = "Created VPA with name: %s"; + private InfoMsgs() { + + } + } + } } diff --git a/src/main/java/com/autotune/analyzer/utils/AnalyzerErrorConstants.java b/src/main/java/com/autotune/analyzer/utils/AnalyzerErrorConstants.java index a279ea77a..de16b4faf 100644 --- a/src/main/java/com/autotune/analyzer/utils/AnalyzerErrorConstants.java +++ b/src/main/java/com/autotune/analyzer/utils/AnalyzerErrorConstants.java @@ -286,4 +286,16 @@ private KruizeRecommendationError() { } } } + + public static final class RecommendationUpdaterErrors { + private RecommendationUpdaterErrors() { + + } + + public static final String UNSUPPORTED_UPDATER_TYPE = "Updater type %s is not supported."; + public static final String GENERATE_RECOMMNEDATION_FAILED = "Failed to generate recommendations for experiment: {}"; + public static final String UPDATER_NOT_INSTALLED = "Updater is not installed."; + public static final String RECOMMENDATION_DATA_NOT_PRESENT = "Recommendations are not present for the experiment."; + public static final String INVALID_VPA_NAME = "VPA name cannot be null or empty."; + } }