diff --git a/pom.xml b/pom.xml index 7c3634b3f..03b50bade 100644 --- a/pom.xml +++ b/pom.xml @@ -23,6 +23,7 @@ 42.3.1 6.1.7.Final 6.1.7.Final + 8.0.1.Final 1.9.9 @@ -154,6 +155,12 @@ hibernate-c3p0 ${hibernatecp30-version} + + org.hibernate.validator + hibernate-validator + ${hibernate-Validator} + + io.micrometer micrometer-core diff --git a/src/main/java/com/autotune/Autotune.java b/src/main/java/com/autotune/Autotune.java index 4905164a0..d364e0cf5 100644 --- a/src/main/java/com/autotune/Autotune.java +++ b/src/main/java/com/autotune/Autotune.java @@ -21,7 +21,6 @@ import com.autotune.analyzer.exceptions.MonitoringAgentNotFoundException; import com.autotune.analyzer.exceptions.MonitoringAgentNotSupportedException; import com.autotune.database.init.KruizeHibernateUtil; -import com.autotune.database.service.ExperimentDBService; import com.autotune.experimentManager.core.ExperimentManager; import com.autotune.operator.InitializeDeployment; import com.autotune.operator.KruizeDeploymentInfo; @@ -34,8 +33,10 @@ import io.prometheus.client.exporter.MetricsServlet; import io.prometheus.client.hotspot.DefaultExports; import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.hibernate.Session; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,8 +54,16 @@ public static void main(String[] args) { ServletContextHandler context = null; disableServerLogging(); + // Create a thread pool with the desired number of threads + QueuedThreadPool threadPool = new QueuedThreadPool(); + threadPool.setMaxThreads(KRUIZE_HTTP_THREAD_POOL_COUNT); // Set the maximum number of threads in the pool + Server server = new Server(threadPool); + // Create a connector (e.g., HTTP) + ServerConnector connector = new ServerConnector(server); + connector.setPort(KRUIZE_SERVER_PORT); + // Set the connector to the server + server.addConnector(connector); - Server server = new Server(KRUIZE_SERVER_PORT); context = new ServletContextHandler(); context.setContextPath(ServerContext.ROOT_CONTEXT); context.setErrorHandler(new KruizeErrorHandler()); @@ -74,7 +83,8 @@ public static void main(String[] args) { try { InitializeDeployment.setup_deployment_info(); - } catch (Exception | K8sTypeNotSupportedException | MonitoringAgentNotSupportedException | MonitoringAgentNotFoundException e) { + } catch (Exception | K8sTypeNotSupportedException | MonitoringAgentNotSupportedException | + MonitoringAgentNotFoundException e) { e.printStackTrace(); System.exit(1); } @@ -105,6 +115,7 @@ public static void main(String[] args) { try { server.start(); + server.join(); } catch (Exception e) { LOGGER.error("Could not start the server!"); e.printStackTrace(); diff --git a/src/main/java/com/autotune/analyzer/exceptions/KruizeErrorHandler.java b/src/main/java/com/autotune/analyzer/exceptions/KruizeErrorHandler.java index 6561ef938..25780d41e 100644 --- a/src/main/java/com/autotune/analyzer/exceptions/KruizeErrorHandler.java +++ b/src/main/java/com/autotune/analyzer/exceptions/KruizeErrorHandler.java @@ -15,6 +15,7 @@ *******************************************************************************/ package com.autotune.analyzer.exceptions; +import com.autotune.analyzer.serviceObjects.UpdateResultsAPIObject; import com.google.gson.Gson; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.servlet.ErrorPageErrorHandler; @@ -23,6 +24,7 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; +import java.util.List; import static com.autotune.analyzer.utils.AnalyzerConstants.ServiceConstants.CHARACTER_ENCODING; import static com.autotune.analyzer.utils.AnalyzerConstants.ServiceConstants.JSON_CONTENT_TYPE; @@ -41,9 +43,12 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques response.setCharacterEncoding(CHARACTER_ENCODING); String origMessage = (String) request.getAttribute("javax.servlet.error.message"); int errorCode = response.getStatus(); + List myList = (List) request.getAttribute("data"); + + PrintWriter out = response.getWriter(); out.append( - new Gson().toJson(new KruizeResponse(origMessage, errorCode, "", "ERROR"))); + new Gson().toJson(new KruizeResponse(origMessage, errorCode, "", "ERROR", myList))); out.flush(); } } diff --git a/src/main/java/com/autotune/analyzer/exceptions/KruizeResponse.java b/src/main/java/com/autotune/analyzer/exceptions/KruizeResponse.java index 18755f23a..fdd177f5e 100644 --- a/src/main/java/com/autotune/analyzer/exceptions/KruizeResponse.java +++ b/src/main/java/com/autotune/analyzer/exceptions/KruizeResponse.java @@ -15,12 +15,19 @@ *******************************************************************************/ package com.autotune.analyzer.exceptions; +import com.autotune.analyzer.serviceObjects.UpdateResultsAPIObject; + +import java.util.List; + public class KruizeResponse { private String message; private int httpcode; private String documentationLink; private String status; + private List data; + + public KruizeResponse(String message, int httpcode, String documentationLink, String status) { this.message = message; this.httpcode = httpcode; @@ -28,6 +35,14 @@ public KruizeResponse(String message, int httpcode, String documentationLink, St this.status = status; } + public KruizeResponse(String message, int httpcode, String documentationLink, String status, List data) { + this.message = message; + this.httpcode = httpcode; + this.documentationLink = documentationLink; + this.status = status; + this.data = data; + } + public String getMessage() { return message; } diff --git a/src/main/java/com/autotune/analyzer/experiment/ExperimentInitiator.java b/src/main/java/com/autotune/analyzer/experiment/ExperimentInitiator.java index 21ee37453..8247c11ba 100644 --- a/src/main/java/com/autotune/analyzer/experiment/ExperimentInitiator.java +++ b/src/main/java/com/autotune/analyzer/experiment/ExperimentInitiator.java @@ -16,18 +16,28 @@ package com.autotune.analyzer.experiment; import com.autotune.analyzer.kruizeObject.KruizeObject; -import com.autotune.analyzer.performanceProfiles.PerformanceProfile; import com.autotune.analyzer.performanceProfiles.PerformanceProfileInterface.PerfProfileInterface; +import com.autotune.analyzer.serviceObjects.Converters; +import com.autotune.analyzer.serviceObjects.UpdateResultsAPIObject; import com.autotune.analyzer.utils.AnalyzerConstants; import com.autotune.common.data.ValidationOutputData; import com.autotune.common.data.result.ExperimentResultData; +import com.autotune.database.service.ExperimentDBService; +import com.google.gson.annotations.SerializedName; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import org.hibernate.validator.HibernateValidator; +import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.http.HttpServletResponse; +import java.lang.reflect.Field; import java.sql.Timestamp; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Set; /** * Initiates new experiment data validations and push into queue for worker to @@ -36,9 +46,10 @@ public class ExperimentInitiator { private static final long serialVersionUID = 1L; private static final Logger LOGGER = LoggerFactory.getLogger(ExperimentInitiator.class); + List successUpdateResultsAPIObjects = new ArrayList<>(); + List failedUpdateResultsAPIObjects = new ArrayList<>(); private ValidationOutputData validationOutputData; - /** * Initiate Experiment validation * @@ -68,36 +79,6 @@ public void validateAndAddNewExperiments( } } - /** - * @param mainKruizeExperimentMap - * @param experimentResultDataList - * @param performanceProfilesMap - */ - public ValidationOutputData validateAndUpdateResults( - Map mainKruizeExperimentMap, - List experimentResultDataList, - Map performanceProfilesMap) { - ValidationOutputData validationOutputData = new ValidationOutputData(false, null, null); - try { - ExperimentResultValidation experimentResultValidation = new ExperimentResultValidation(mainKruizeExperimentMap, performanceProfilesMap); - experimentResultValidation.validate(experimentResultDataList, performanceProfilesMap); - if (experimentResultValidation.isSuccess()) { - ExperimentInterface experimentInterface = new ExperimentInterfaceImpl(); - experimentInterface.addResultsToLocalStorage(mainKruizeExperimentMap, experimentResultDataList); - validationOutputData.setSuccess(true); - } else { - validationOutputData.setSuccess(false); - validationOutputData.setMessage("Validation failed: " + experimentResultValidation.getErrorMessage()); - } - } catch (Exception e) { - LOGGER.error("Validate and push experiment falied: " + e.getMessage()); - validationOutputData.setSuccess(false); - validationOutputData.setMessage("Exception occurred while validating the result data: " + e.getMessage()); - validationOutputData.setErrorCode(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - } - return validationOutputData; - } - // Generate recommendations and add it to the kruize object public void generateAndAddRecommendations(KruizeObject kruizeObject, List experimentResultDataList, Timestamp interval_start_time, Timestamp interval_end_time) throws Exception { if (AnalyzerConstants.PerformanceProfileConstants.perfProfileInstances.containsKey(kruizeObject.getPerformanceProfile())) { @@ -113,4 +94,71 @@ public void generateAndAddRecommendations(KruizeObject kruizeObject, List updateResultsAPIObjects) { + List failedDBObjects = new ArrayList<>(); + Validator validator = Validation.byProvider(HibernateValidator.class) + .configure() + .messageInterpolator(new ParameterMessageInterpolator()) + .failFast(false) + .buildValidatorFactory() + .getValidator(); + + for (UpdateResultsAPIObject object : updateResultsAPIObjects) { + Set> violations = validator.validate(object, UpdateResultsAPIObject.FullValidationSequence.class); + if (violations.isEmpty()) { + successUpdateResultsAPIObjects.add(object); + } else { + List errorReasons = new ArrayList<>(); + for (ConstraintViolation violation : violations) { + String propertyPath = violation.getPropertyPath().toString(); + if (null != propertyPath && propertyPath.length() != 0) { + errorReasons.add(getSerializedName(propertyPath, UpdateResultsAPIObject.class) + ": " + violation.getMessage()); + } else { + errorReasons.add(violation.getMessage()); + } + } + object.setErrorReasons(errorReasons); + failedUpdateResultsAPIObjects.add(object); + } + } + List resultDataList = new ArrayList<>(); + successUpdateResultsAPIObjects.forEach( + (successObj) -> { + resultDataList.add(Converters.KruizeObjectConverters.convertUpdateResultsAPIObjToExperimentResultData(successObj)); + } + ); + + if (successUpdateResultsAPIObjects.size() > 0) { + failedDBObjects = new ExperimentDBService().addResultsToDB(resultDataList); + failedUpdateResultsAPIObjects.addAll(failedDBObjects); + } + } + + public String getSerializedName(String fieldName, Class targetClass) { + Class currentClass = targetClass; + while (currentClass != null) { + try { + Field field = currentClass.getDeclaredField(fieldName); + SerializedName annotation = field.getAnnotation(SerializedName.class); + if (annotation != null) { + fieldName = annotation.value(); + } + } catch (NoSuchFieldException e) { + // Field not found in the current class + // Move up to the superclass + currentClass = currentClass.getSuperclass(); + } + } + return fieldName; + } + + public List getSuccessUpdateResultsAPIObjects() { + return successUpdateResultsAPIObjects; + } + + public List getFailedUpdateResultsAPIObjects() { + return failedUpdateResultsAPIObjects; + } + + } diff --git a/src/main/java/com/autotune/analyzer/experiment/ExperimentResultValidation.java b/src/main/java/com/autotune/analyzer/experiment/ExperimentResultValidation.java index a9d5ae2d9..4c15404fb 100644 --- a/src/main/java/com/autotune/analyzer/experiment/ExperimentResultValidation.java +++ b/src/main/java/com/autotune/analyzer/experiment/ExperimentResultValidation.java @@ -15,209 +15,17 @@ *******************************************************************************/ package com.autotune.analyzer.experiment; -import com.autotune.analyzer.kruizeObject.KruizeObject; -import com.autotune.analyzer.performanceProfiles.PerformanceProfile; -import com.autotune.analyzer.performanceProfiles.utils.PerformanceProfileUtil; -import com.autotune.analyzer.utils.AnalyzerConstants; -import com.autotune.analyzer.utils.AnalyzerErrorConstants; -import com.autotune.common.data.ValidationOutputData; -import com.autotune.common.data.result.ExperimentResultData; -import com.autotune.common.data.result.IntervalResults; -import com.autotune.database.service.ExperimentDBService; -import com.autotune.utils.KruizeConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.http.HttpServletResponse; -import java.util.List; -import java.util.Map; - /** * Util class to validate input request related to Experiment Results for metrics collection. */ public class ExperimentResultValidation { private static final Logger LOGGER = LoggerFactory.getLogger(ExperimentResultValidation.class); - private boolean success; - private String errorMessage; - private Map mainKruizeExperimentMAP; - private Map performanceProfileMap; - - public ExperimentResultValidation(Map mainKruizeExperimentMAP, Map performanceProfileMap) { - this.mainKruizeExperimentMAP = mainKruizeExperimentMAP; - this.performanceProfileMap = performanceProfileMap; - } - - public void validate(List experimentResultDataList, Map performanceProfilesMap) { - try { - boolean proceed = false; - String errorMsg = ""; - for (ExperimentResultData resultData : experimentResultDataList) { - String expName = resultData.getExperiment_name(); - if (null != expName && !expName.isEmpty() && null != resultData.getIntervalEndTime() && null != resultData.getIntervalStartTime()) { - try { - new ExperimentDBService().loadExperimentFromDBByName(mainKruizeExperimentMAP, expName); - } catch (Exception e) { - LOGGER.error("Loading saved experiment {} failed: {} ", expName, e.getMessage()); - } - - // Check if there is only one K8sObject in resultData - if (resultData.getKubernetes_objects().size() > 1) { - errorMsg = errorMsg.concat(String.format("Bulk Kubernetes Objects are unsupported!")); - resultData.setValidationOutputData(new ValidationOutputData(false, errorMsg, HttpServletResponse.SC_BAD_REQUEST)); - break; - } - - if (mainKruizeExperimentMAP.containsKey(expName)) { - KruizeObject kruizeObject = mainKruizeExperimentMAP.get(resultData.getExperiment_name()); - // check if the intervalEndTime is greater than intervalStartTime and interval duration is greater than measurement duration - IntervalResults intervalResults = new IntervalResults(resultData.getIntervalStartTime(), resultData.getIntervalEndTime()); - Double durationInSeconds = intervalResults.getDuration_in_seconds(); - String measurementDurationInMins = kruizeObject.getTrial_settings().getMeasurement_durationMinutes(); - LOGGER.debug("Duration in seconds = {}", intervalResults.getDuration_in_seconds()); - if (durationInSeconds < 0) { - errorMsg = errorMsg.concat(AnalyzerErrorConstants.AutotuneObjectErrors.WRONG_TIMESTAMP); - resultData.setValidationOutputData(new ValidationOutputData(false, errorMsg, HttpServletResponse.SC_BAD_REQUEST)); - break; - } else { - Double parsedMeasurementDuration = Double.parseDouble(measurementDurationInMins.substring(0, measurementDurationInMins.length() - 3)); - // Calculate the lower and upper bounds for the acceptable range i.e. +-5 seconds - double lowerRange = Math.abs((parsedMeasurementDuration * KruizeConstants.TimeConv.NO_OF_SECONDS_PER_MINUTE) - (KruizeConstants.TimeConv.MEASUREMENT_DURATION_THRESHOLD_SECONDS)); - double upperRange = (parsedMeasurementDuration * KruizeConstants.TimeConv.NO_OF_SECONDS_PER_MINUTE) + (KruizeConstants.TimeConv.MEASUREMENT_DURATION_THRESHOLD_SECONDS); - if (!(durationInSeconds >= lowerRange && durationInSeconds <= upperRange)) { - errorMsg = errorMsg.concat(AnalyzerErrorConstants.AutotuneObjectErrors.MEASUREMENT_DURATION_ERROR); - resultData.setValidationOutputData(new ValidationOutputData(false, errorMsg, HttpServletResponse.SC_BAD_REQUEST)); - break; - } - } - boolean kubeObjsMisMatch = false; - // Check if Kubernetes Object Type is matched - String kubeObjTypeInKruizeObject = kruizeObject.getKubernetes_objects().get(0).getType(); - String kubeObjTypeInResultData = resultData.getKubernetes_objects().get(0).getType(); - - if (!kubeObjTypeInKruizeObject.equals(kubeObjTypeInResultData)) { - kubeObjsMisMatch = true; - errorMsg = errorMsg.concat( - String.format( - "Kubernetes Object Types MisMatched. Expected Type: %s, Found: %s in Results for experiment: %s \n", - kubeObjTypeInKruizeObject, - kubeObjTypeInResultData, - expName - )); - } - - // Check if Kubernetes Object Name is matched - String kubeObjNameInKruizeObject = kruizeObject.getKubernetes_objects().get(0).getName(); - String kubeObjNameInResultsData = resultData.getKubernetes_objects().get(0).getName(); - if (!kubeObjNameInKruizeObject.equals(kubeObjNameInResultsData)) { - kubeObjsMisMatch = true; - errorMsg = errorMsg.concat( - String.format( - "Kubernetes Object Names MisMatched. Expected Name: %s, Found: %s in Results for experiment: %s \n", - kubeObjNameInKruizeObject, - kubeObjNameInResultsData, - expName - )); - } - - // Check if Kubernetes Object NameSpace is matched - String kubeObjNameSpaceInKruizeObject = kruizeObject.getKubernetes_objects().get(0).getNamespace(); - String kubeObjNameSpaceInResultsData = resultData.getKubernetes_objects().get(0).getNamespace(); - - if (!kubeObjNameSpaceInKruizeObject.equals(kubeObjNameSpaceInResultsData)) { - kubeObjsMisMatch = true; - errorMsg = errorMsg.concat( - String.format( - "Kubernetes Object Namespaces MisMatched. Expected Namespace: %s, Found: %s in Results for experiment: %s \n", - kubeObjNameSpaceInKruizeObject, - kubeObjNameSpaceInResultsData, - expName - )); - } - - if (kubeObjsMisMatch) { - resultData.setValidationOutputData(new ValidationOutputData(false, errorMsg, HttpServletResponse.SC_BAD_REQUEST)); - break; - } - /* - Fetch the performance profile from the Map corresponding to the name in the kruize object, - and then validate the Performance Profile data - */ - try { - LOGGER.debug("Kruize Object: {}", kruizeObject); - // fetch the Performance Profile from the DB - try { - new ExperimentDBService().loadPerformanceProfileFromDBByName(performanceProfilesMap, kruizeObject.getPerformanceProfile()); - } catch (Exception e) { - LOGGER.error("Loading saved Performance Profile {} failed: {} ", expName, e.getMessage()); - } - PerformanceProfile performanceProfile = performanceProfilesMap.get(kruizeObject.getPerformanceProfile()); - // validate the 'resultdata' with the performance profile - errorMsg = PerformanceProfileUtil.validateResults(performanceProfile, resultData); - if (null == errorMsg || errorMsg.isEmpty()) { - proceed = true; - } else { - proceed = false; - resultData.setValidationOutputData(new ValidationOutputData(false, errorMsg, HttpServletResponse.SC_BAD_REQUEST)); - break; - } - } catch (Exception e) { - LOGGER.error("Caught Exception: {}", e); - errorMsg = "Validation failed: " + e.getMessage(); - proceed = false; - resultData.setValidationOutputData(new ValidationOutputData(false, errorMsg, HttpServletResponse.SC_INTERNAL_SERVER_ERROR)); - break; - } - } else { - proceed = false; - errorMsg = errorMsg.concat(String.format("Experiment name: %s not found", resultData.getExperiment_name())); - resultData.setValidationOutputData(new ValidationOutputData(false, errorMsg, HttpServletResponse.SC_BAD_REQUEST)); - break; - } - resultData.setValidationOutputData(new ValidationOutputData(true, AnalyzerConstants.ServiceConstants.RESULT_SAVED, HttpServletResponse.SC_CREATED)); - } else { - errorMsg = errorMsg.concat("experiment_name and timestamp are mandatory fields and they cannot be empty"); - proceed = false; - resultData.setValidationOutputData(new ValidationOutputData(false, errorMsg, HttpServletResponse.SC_BAD_REQUEST)); - break; - } - } - if (proceed) - setSuccess(true); - else - markFailed(errorMsg); - } catch (Exception e) { - e.printStackTrace(); - setErrorMessage("Validation failed: " + e.getMessage()); - } - } - - public void markFailed(String message) { - setSuccess(false); - setErrorMessage(message); - } - - public boolean isSuccess() { - return success; - } +} - public void setSuccess(boolean success) { - this.success = success; - } - public String getErrorMessage() { - return errorMessage; - } - public void setErrorMessage(String errorMessage) { - this.errorMessage = errorMessage; - } - @Override - public String toString() { - return "ExperimentResultValidation{" + - "success=" + success + - ", errorMessage='" + errorMessage + '\'' + - '}'; - } -} diff --git a/src/main/java/com/autotune/analyzer/performanceProfiles/PerformanceProfileInterface/ResourceOptimizationOpenshiftImpl.java b/src/main/java/com/autotune/analyzer/performanceProfiles/PerformanceProfileInterface/ResourceOptimizationOpenshiftImpl.java index 109dc406b..f06d92e91 100644 --- a/src/main/java/com/autotune/analyzer/performanceProfiles/PerformanceProfileInterface/ResourceOptimizationOpenshiftImpl.java +++ b/src/main/java/com/autotune/analyzer/performanceProfiles/PerformanceProfileInterface/ResourceOptimizationOpenshiftImpl.java @@ -107,7 +107,6 @@ public void generateRecommendation(KruizeObject kruizeObject, List kubernetesAPIObjects; + private List errorReasons; + public Timestamp getStartTimestamp() { return startTimestamp; } @@ -53,6 +76,14 @@ public void setKubernetesObjects(List kubernetesAPIObjects) this.kubernetesAPIObjects = kubernetesAPIObjects; } + public List getErrorReasons() { + return errorReasons; + } + + public void setErrorReasons(List errorReasons) { + this.errorReasons = errorReasons; + } + @Override public String toString() { return "UpdateResultsAPIObject{" + @@ -61,4 +92,13 @@ public String toString() { ", kubernetesAPIObjects=" + kubernetesAPIObjects + '}'; } + + public interface EvaluateRemainingConstraints { + } + + @GroupSequence({UpdateResultsAPIObject.class, InitialValidation.class, ExperimentNameExistValidation.class, EvaluateRemainingConstraints.class}) + public interface FullValidationSequence { + } + + } diff --git a/src/main/java/com/autotune/analyzer/serviceObjects/verification/annotators/CompareDate.java b/src/main/java/com/autotune/analyzer/serviceObjects/verification/annotators/CompareDate.java new file mode 100644 index 000000000..14ff5c7b5 --- /dev/null +++ b/src/main/java/com/autotune/analyzer/serviceObjects/verification/annotators/CompareDate.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2023 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.serviceObjects.verification.annotators; + + +import com.autotune.analyzer.serviceObjects.verification.validators.CompareDateValidator; +import com.autotune.analyzer.utils.AnalyzerErrorConstants; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = CompareDateValidator.class) +@Documented +public @interface CompareDate { + String message() default AnalyzerErrorConstants.AutotuneObjectErrors.WRONG_TIMESTAMP; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/src/main/java/com/autotune/analyzer/serviceObjects/verification/annotators/ExperimentNameExist.java b/src/main/java/com/autotune/analyzer/serviceObjects/verification/annotators/ExperimentNameExist.java new file mode 100644 index 000000000..8d0724ecb --- /dev/null +++ b/src/main/java/com/autotune/analyzer/serviceObjects/verification/annotators/ExperimentNameExist.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2023 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.serviceObjects.verification.annotators; + +import com.autotune.analyzer.serviceObjects.verification.validators.ExperimentNameExistValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = ExperimentNameExistValidator.class) +public @interface ExperimentNameExist { + String message() default "Data does not match DB records"; + + Class[] groups() default {}; + + Class[] payload() default {}; + + +} diff --git a/src/main/java/com/autotune/analyzer/serviceObjects/verification/annotators/KubernetesElementsCheck.java b/src/main/java/com/autotune/analyzer/serviceObjects/verification/annotators/KubernetesElementsCheck.java new file mode 100644 index 000000000..bc1c72983 --- /dev/null +++ b/src/main/java/com/autotune/analyzer/serviceObjects/verification/annotators/KubernetesElementsCheck.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2023 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.serviceObjects.verification.annotators; + +import com.autotune.analyzer.serviceObjects.verification.validators.KubernetesElementsValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = KubernetesElementsValidator.class) +public @interface KubernetesElementsCheck { + String message() default "Data does not match DB records"; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/src/main/java/com/autotune/analyzer/serviceObjects/verification/annotators/PerformanceProfileCheck.java b/src/main/java/com/autotune/analyzer/serviceObjects/verification/annotators/PerformanceProfileCheck.java new file mode 100644 index 000000000..be562fd09 --- /dev/null +++ b/src/main/java/com/autotune/analyzer/serviceObjects/verification/annotators/PerformanceProfileCheck.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2023 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.serviceObjects.verification.annotators; + +import com.autotune.analyzer.serviceObjects.verification.validators.PerformanceProfileValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = PerformanceProfileValidator.class) +@Documented +public @interface PerformanceProfileCheck { + String message() default ""; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/src/main/java/com/autotune/analyzer/serviceObjects/verification/annotators/TimeDifferenceCheck.java b/src/main/java/com/autotune/analyzer/serviceObjects/verification/annotators/TimeDifferenceCheck.java new file mode 100644 index 000000000..cb3c9c83c --- /dev/null +++ b/src/main/java/com/autotune/analyzer/serviceObjects/verification/annotators/TimeDifferenceCheck.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2023 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.serviceObjects.verification.annotators; + + +import com.autotune.analyzer.serviceObjects.verification.validators.TimeDifferenceValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = TimeDifferenceValidator.class) +@Documented +public @interface TimeDifferenceCheck { + String message() default ""; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/src/main/java/com/autotune/analyzer/serviceObjects/verification/validators/CompareDateValidator.java b/src/main/java/com/autotune/analyzer/serviceObjects/verification/validators/CompareDateValidator.java new file mode 100644 index 000000000..caef8701e --- /dev/null +++ b/src/main/java/com/autotune/analyzer/serviceObjects/verification/validators/CompareDateValidator.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2023 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.serviceObjects.verification.validators; + +import com.autotune.analyzer.serviceObjects.UpdateResultsAPIObject; +import com.autotune.analyzer.serviceObjects.verification.annotators.CompareDate; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class CompareDateValidator implements ConstraintValidator { + private static final Logger LOGGER = LoggerFactory.getLogger(CompareDateValidator.class); + + @Override + public void initialize(CompareDate constraintAnnotation) { + } + + @Override + public boolean isValid(UpdateResultsAPIObject updateResultsAPIObject, ConstraintValidatorContext context) { + boolean success = false; + if (null != updateResultsAPIObject.getStartTimestamp() && null != updateResultsAPIObject.getEndTimestamp()) { + int comparisonResult = updateResultsAPIObject.getStartTimestamp().compareTo(updateResultsAPIObject.getEndTimestamp()); + if (comparisonResult < 0) { + success = true; + } + } + return success; + } +} diff --git a/src/main/java/com/autotune/analyzer/serviceObjects/verification/validators/ExperimentNameExistValidator.java b/src/main/java/com/autotune/analyzer/serviceObjects/verification/validators/ExperimentNameExistValidator.java new file mode 100644 index 000000000..2e9c64e9a --- /dev/null +++ b/src/main/java/com/autotune/analyzer/serviceObjects/verification/validators/ExperimentNameExistValidator.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2023 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.serviceObjects.verification.validators; + +import com.autotune.analyzer.kruizeObject.KruizeObject; +import com.autotune.analyzer.serviceObjects.verification.annotators.ExperimentNameExist; +import com.autotune.database.service.ExperimentDBService; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + +public class ExperimentNameExistValidator implements ConstraintValidator { + private static final Logger LOGGER = LoggerFactory.getLogger(ExperimentNameExistValidator.class); + static Map mainKruizeExperimentMAP = new HashMap<>(); + + + // You can inject your database access/repository here to fetch the data + + @Override + public boolean isValid(String experimentName, ConstraintValidatorContext context) { + boolean success = false; + String errorMessage = ""; + if (!mainKruizeExperimentMAP.containsKey(experimentName)) { + // Retrieve the data from the database + try { + new ExperimentDBService().loadExperimentFromDBByName(mainKruizeExperimentMAP, experimentName); + } catch (Exception e) { + LOGGER.error("Loading saved experiment {} failed: {} ", experimentName, e.getMessage()); + errorMessage = String.format("failed to load from DB due to %s", e.getMessage()); + } + } + + if (mainKruizeExperimentMAP.containsKey(experimentName)) { + success = true; + } else { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(String.format("%s not found %s", experimentName, errorMessage)) + .addPropertyNode("") + .addConstraintViolation(); + } + return success; + } +} + diff --git a/src/main/java/com/autotune/analyzer/serviceObjects/verification/validators/KubernetesElementsValidator.java b/src/main/java/com/autotune/analyzer/serviceObjects/verification/validators/KubernetesElementsValidator.java new file mode 100644 index 000000000..332d53fdb --- /dev/null +++ b/src/main/java/com/autotune/analyzer/serviceObjects/verification/validators/KubernetesElementsValidator.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2023 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.serviceObjects.verification.validators; + +import com.autotune.analyzer.kruizeObject.KruizeObject; +import com.autotune.analyzer.performanceProfiles.PerformanceProfile; +import com.autotune.analyzer.serviceObjects.Converters; +import com.autotune.analyzer.serviceObjects.UpdateResultsAPIObject; +import com.autotune.analyzer.serviceObjects.verification.annotators.KubernetesElementsCheck; +import com.autotune.analyzer.services.UpdateResults; +import com.autotune.common.data.result.ExperimentResultData; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +public class KubernetesElementsValidator implements ConstraintValidator { + @Override + public void initialize(KubernetesElementsCheck constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(UpdateResultsAPIObject updateResultsAPIObject, ConstraintValidatorContext context) { + boolean success = false; + try { + KruizeObject kruizeObject = ExperimentNameExistValidator.mainKruizeExperimentMAP.get(updateResultsAPIObject.getExperimentName()); + PerformanceProfile performanceProfile = UpdateResults.performanceProfilesMap.get(kruizeObject.getPerformanceProfile()); + ExperimentResultData resultData = Converters.KruizeObjectConverters.convertUpdateResultsAPIObjToExperimentResultData(updateResultsAPIObject); + String expName = kruizeObject.getExperimentName(); + String errorMsg = ""; + boolean kubeObjsMisMatch = false; + + // Check if Kubernetes Object Type is matched + String kubeObjTypeInKruizeObject = kruizeObject.getKubernetes_objects().get(0).getType(); + String kubeObjTypeInResultData = resultData.getKubernetes_objects().get(0).getType(); + + if (!kubeObjTypeInKruizeObject.equals(kubeObjTypeInResultData)) { + kubeObjsMisMatch = true; + errorMsg = errorMsg.concat( + String.format( + "Kubernetes Object Types MisMatched. Expected Type: %s, Found: %s in Results for experiment: %s \n", + kubeObjTypeInKruizeObject, + kubeObjTypeInResultData, + expName + )); + } + + // Check if Kubernetes Object Name is matched + String kubeObjNameInKruizeObject = kruizeObject.getKubernetes_objects().get(0).getName(); + String kubeObjNameInResultsData = resultData.getKubernetes_objects().get(0).getName(); + + if (!kubeObjNameInKruizeObject.equals(kubeObjNameInResultsData)) { + kubeObjsMisMatch = true; + errorMsg = errorMsg.concat( + String.format( + "Kubernetes Object Names MisMatched. Expected Name: %s, Found: %s in Results for experiment: %s \n", + kubeObjNameInKruizeObject, + kubeObjNameInResultsData, + expName + )); + } + + // Check if Kubernetes Object NameSpace is matched + String kubeObjNameSpaceInKruizeObject = kruizeObject.getKubernetes_objects().get(0).getNamespace(); + String kubeObjNameSpaceInResultsData = resultData.getKubernetes_objects().get(0).getNamespace(); + + if (!kubeObjNameSpaceInKruizeObject.equals(kubeObjNameSpaceInResultsData)) { + kubeObjsMisMatch = true; + errorMsg = errorMsg.concat( + String.format( + "Kubernetes Object Namespaces MisMatched. Expected Namespace: %s, Found: %s in Results for experiment: %s \n", + kubeObjNameSpaceInKruizeObject, + kubeObjNameSpaceInResultsData, + expName + )); + } + + if (kubeObjsMisMatch) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(errorMsg) + .addPropertyNode("kubernetes_objects ") + .addConstraintViolation(); + } else { + success = true; + } + } catch (Exception e) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(e.getMessage()) + .addPropertyNode("Kubernetes Elements") + .addConstraintViolation(); + } + + return success; + } +} diff --git a/src/main/java/com/autotune/analyzer/serviceObjects/verification/validators/PerformanceProfileValidator.java b/src/main/java/com/autotune/analyzer/serviceObjects/verification/validators/PerformanceProfileValidator.java new file mode 100644 index 000000000..676e24563 --- /dev/null +++ b/src/main/java/com/autotune/analyzer/serviceObjects/verification/validators/PerformanceProfileValidator.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2023 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.serviceObjects.verification.validators; + +import com.autotune.analyzer.kruizeObject.KruizeObject; +import com.autotune.analyzer.performanceProfiles.PerformanceProfile; +import com.autotune.analyzer.performanceProfiles.utils.PerformanceProfileUtil; +import com.autotune.analyzer.serviceObjects.Converters; +import com.autotune.analyzer.serviceObjects.UpdateResultsAPIObject; +import com.autotune.analyzer.serviceObjects.verification.annotators.PerformanceProfileCheck; +import com.autotune.analyzer.services.UpdateResults; +import com.autotune.common.data.result.ExperimentResultData; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + +public class PerformanceProfileValidator implements ConstraintValidator { + private static final Logger LOGGER = LoggerFactory.getLogger(PerformanceProfileValidator.class); + + @Override + public void initialize(PerformanceProfileCheck constraintAnnotation) { + } + + @Override + public boolean isValid(UpdateResultsAPIObject updateResultsAPIObject, ConstraintValidatorContext context) { + boolean success = false; + /* + Fetch the performance profile from the Map corresponding to the name in the kruize object, + and then validate the Performance Profile data + */ + try { + KruizeObject kruizeObject = ExperimentNameExistValidator.mainKruizeExperimentMAP.get(updateResultsAPIObject.getExperimentName()); + Map performanceProfilesMap = new HashMap<>(); + PerformanceProfile performanceProfile = UpdateResults.performanceProfilesMap.get(kruizeObject.getPerformanceProfile()); + ExperimentResultData resultData = Converters.KruizeObjectConverters.convertUpdateResultsAPIObjToExperimentResultData(updateResultsAPIObject); + // validate the 'resultdata' with the performance profile + String errorMsg = PerformanceProfileUtil.validateResults(performanceProfile, resultData); + if (null == errorMsg || errorMsg.isEmpty()) { + success = true; + } else { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(errorMsg) + .addPropertyNode("Performance profile") + .addConstraintViolation(); + } + } catch (Exception e) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(e.getMessage()) + .addPropertyNode("Performance profile") + .addConstraintViolation(); + } + return success; + } +} diff --git a/src/main/java/com/autotune/analyzer/serviceObjects/verification/validators/TimeDifferenceValidator.java b/src/main/java/com/autotune/analyzer/serviceObjects/verification/validators/TimeDifferenceValidator.java new file mode 100644 index 000000000..12a29f960 --- /dev/null +++ b/src/main/java/com/autotune/analyzer/serviceObjects/verification/validators/TimeDifferenceValidator.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2023 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.serviceObjects.verification.validators; + + +import com.autotune.analyzer.kruizeObject.KruizeObject; +import com.autotune.analyzer.serviceObjects.UpdateResultsAPIObject; +import com.autotune.analyzer.serviceObjects.verification.annotators.TimeDifferenceCheck; +import com.autotune.common.data.result.IntervalResults; +import com.autotune.utils.KruizeConstants; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class TimeDifferenceValidator implements ConstraintValidator { + private static final Logger LOGGER = LoggerFactory.getLogger(TimeDifferenceValidator.class); + + @Override + public void initialize(TimeDifferenceCheck constraintAnnotation) { + } + + @Override + public boolean isValid(UpdateResultsAPIObject updateResultsAPIObject, ConstraintValidatorContext context) { + boolean success = false; + + KruizeObject kruizeObject = ExperimentNameExistValidator.mainKruizeExperimentMAP.get(updateResultsAPIObject.getExperimentName()); + + IntervalResults intervalResults = new IntervalResults(updateResultsAPIObject.getStartTimestamp(), updateResultsAPIObject.getEndTimestamp()); + Double durationInSeconds = intervalResults.getDuration_in_seconds(); + String measurementDurationInMins = kruizeObject.getTrial_settings().getMeasurement_durationMinutes(); + Double parsedMeasurementDuration = Double.parseDouble(measurementDurationInMins.substring(0, measurementDurationInMins.length() - 3)); + // Calculate the lower and upper bounds for the acceptable range i.e. +-5 seconds + double lowerRange = Math.abs((parsedMeasurementDuration * KruizeConstants.TimeConv.NO_OF_SECONDS_PER_MINUTE) - (KruizeConstants.TimeConv.MEASUREMENT_DURATION_THRESHOLD_SECONDS)); + double upperRange = (parsedMeasurementDuration * KruizeConstants.TimeConv.NO_OF_SECONDS_PER_MINUTE) + (KruizeConstants.TimeConv.MEASUREMENT_DURATION_THRESHOLD_SECONDS); + if ((durationInSeconds >= lowerRange && durationInSeconds <= upperRange)) + success = true; + + return success; + } +} \ No newline at end of file diff --git a/src/main/java/com/autotune/analyzer/services/PerformanceProfileService.java b/src/main/java/com/autotune/analyzer/services/PerformanceProfileService.java index 20bf5d1e3..71be6267e 100644 --- a/src/main/java/com/autotune/analyzer/services/PerformanceProfileService.java +++ b/src/main/java/com/autotune/analyzer/services/PerformanceProfileService.java @@ -18,14 +18,14 @@ import com.autotune.analyzer.exceptions.InvalidValueException; import com.autotune.analyzer.exceptions.PerformanceProfileResponse; -import com.autotune.analyzer.serviceObjects.Converters; +import com.autotune.analyzer.performanceProfiles.PerformanceProfile; import com.autotune.analyzer.performanceProfiles.utils.PerformanceProfileUtil; +import com.autotune.analyzer.serviceObjects.Converters; +import com.autotune.analyzer.utils.AnalyzerConstants; +import com.autotune.analyzer.utils.AnalyzerErrorConstants; import com.autotune.analyzer.utils.GsonUTCDateAdapter; import com.autotune.common.data.ValidationOutputData; import com.autotune.common.data.metrics.Metric; -import com.autotune.analyzer.performanceProfiles.PerformanceProfile; -import com.autotune.analyzer.utils.AnalyzerConstants; -import com.autotune.analyzer.utils.AnalyzerErrorConstants; import com.autotune.database.service.ExperimentDBService; import com.google.gson.ExclusionStrategy; import com.google.gson.FieldAttributes; @@ -45,7 +45,6 @@ import java.io.Serial; import java.util.Collection; import java.util.Date; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @@ -61,11 +60,13 @@ public class PerformanceProfileService extends HttpServlet { @Serial private static final long serialVersionUID = 1L; private static final Logger LOGGER = LoggerFactory.getLogger(PerformanceProfileService.class); - + private ConcurrentHashMap performanceProfilesMap; @Override public void init(ServletConfig config) throws ServletException { super.init(config); + performanceProfilesMap = (ConcurrentHashMap) getServletContext() + .getAttribute(AnalyzerConstants.PerformanceProfileConstants.PERF_PROFILE_MAP); } /** @@ -75,7 +76,6 @@ public void init(ServletConfig config) throws ServletException { * @param response * @throws IOException */ - @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { try { @@ -84,21 +84,20 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) PerformanceProfile performanceProfile = Converters.KruizeObjectConverters.convertInputJSONToCreatePerfProfile(inputData); ValidationOutputData validationOutputData = PerformanceProfileUtil.validateAndAddProfile(performanceProfilesMap, performanceProfile); if (validationOutputData.isSuccess()) { - ValidationOutputData addedToDB; - addedToDB = new ExperimentDBService().addPerformanceProfileToDB(performanceProfile); + ValidationOutputData addedToDB = new ExperimentDBService().addPerformanceProfileToDB(performanceProfile); if (addedToDB.isSuccess()) { + performanceProfilesMap.put(performanceProfile.getName(), performanceProfile); + getServletContext().setAttribute(AnalyzerConstants.PerformanceProfileConstants.PERF_PROFILE_MAP, performanceProfilesMap); LOGGER.debug("Added Performance Profile : {} into the DB with version: {}", performanceProfile.getName(), performanceProfile.getProfile_version()); sendSuccessResponse(response, "Performance Profile : " + performanceProfile.getName() + " created successfully."); - } - else { + } else { sendErrorResponse(response, null, HttpServletResponse.SC_BAD_REQUEST, addedToDB.getMessage()); } - } - else + } else sendErrorResponse(response, null, validationOutputData.getErrorCode(), validationOutputData.getMessage()); } catch (Exception e) { - sendErrorResponse(response, e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,"Validation failed: " + e.getMessage()); + sendErrorResponse(response, e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Validation failed: " + e.getMessage()); } catch (InvalidValueException e) { throw new RuntimeException(e); } @@ -106,6 +105,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) /** * Get List of Performance Profiles + * * @param req * @param response * @throws ServletException @@ -117,7 +117,6 @@ protected void doGet(HttpServletRequest req, HttpServletResponse response) throw response.setCharacterEncoding(CHARACTER_ENCODING); response.setStatus(HttpServletResponse.SC_OK); String gsonStr = "[]"; - Map performanceProfilesMap = new ConcurrentHashMap<>(); // Fetch all profiles from the DB try { new ExperimentDBService().loadAllPerformanceProfiles(performanceProfilesMap); @@ -136,9 +135,10 @@ protected void doGet(HttpServletRequest req, HttpServletResponse response) throw public boolean shouldSkipField(FieldAttributes f) { return f.getDeclaringClass() == Metric.class && ( f.getName().equals("trialSummaryResult") - || f.getName().equals("cycleDataMap") - ); + || f.getName().equals("cycleDataMap") + ); } + @Override public boolean shouldSkipClass(Class aClass) { return false; @@ -153,8 +153,10 @@ public boolean shouldSkipClass(Class aClass) { response.getWriter().close(); } - /**TODO: Need to implement + /** + * TODO: Need to implement * Update Performance Profile + * * @param req * @param resp * @throws ServletException @@ -165,8 +167,10 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws Se super.doPut(req, resp); } - /**TODO: Need to implement + /** + * TODO: Need to implement * Delete Performance profile + * * @param req * @param resp * @throws ServletException @@ -179,6 +183,7 @@ protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws /** * Send success response in case of no errors or exceptions. + * * @param response * @param message * @throws IOException @@ -200,6 +205,7 @@ private void sendSuccessResponse(HttpServletResponse response, String message) t /** * Send response containing corresponding error message in case of failures and exceptions + * * @param response * @param e * @param httpStatusCode diff --git a/src/main/java/com/autotune/analyzer/services/UpdateRecommendations.java b/src/main/java/com/autotune/analyzer/services/UpdateRecommendations.java index a12cb52bd..13ba9fd22 100644 --- a/src/main/java/com/autotune/analyzer/services/UpdateRecommendations.java +++ b/src/main/java/com/autotune/analyzer/services/UpdateRecommendations.java @@ -17,7 +17,6 @@ import com.autotune.analyzer.experiment.ExperimentInitiator; import com.autotune.analyzer.kruizeObject.KruizeObject; -import com.autotune.analyzer.recommendations.RecommendationConstants; import com.autotune.analyzer.serviceObjects.ContainerAPIObject; import com.autotune.analyzer.serviceObjects.Converters; import com.autotune.analyzer.serviceObjects.ListRecommendationsAPIObject; @@ -81,6 +80,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) // Get the values from the request parameters String experiment_name = request.getParameter(KruizeConstants.JSONKeys.EXPERIMENT_NAME); String intervalEndTimeStr = request.getParameter(KruizeConstants.JSONKeys.INTERVAL_END_TIME); + String intervalStartTimeStr = request.getParameter(KruizeConstants.JSONKeys.INTERVAL_START_TIME); Timestamp interval_end_time = null; Timestamp interval_start_time = null; @@ -96,7 +96,6 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) sendErrorResponse(response, null, HttpServletResponse.SC_BAD_REQUEST, AnalyzerErrorConstants.APIErrors.UpdateRecommendationsAPI.INTERVAL_END_TIME_MANDATORY); return; } - if (!Utils.DateUtils.isAValidDate(KruizeConstants.DateFormats.STANDARD_JSON_DATE_FORMAT, intervalEndTimeStr)) { sendErrorResponse( response, @@ -109,6 +108,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) interval_end_time = Utils.DateUtils.getTimeStampFrom(KruizeConstants.DateFormats.STANDARD_JSON_DATE_FORMAT, intervalEndTimeStr); } + // Check if interval_start_time is provided if (intervalStartTimeStr != null) { if (!Utils.DateUtils.isAValidDate(KruizeConstants.DateFormats.STANDARD_JSON_DATE_FORMAT, intervalStartTimeStr)) { @@ -139,12 +139,11 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) } LOGGER.debug("experiment_name : {} and interval_start_time : {} and interval_end_time : {} ", experiment_name, intervalStartTimeStr, intervalEndTimeStr); - List experimentResultDataList = null; - + List experimentResultDataList = null; + ExperimentResultData experimentResultData = null; try { experimentResultDataList = new ExperimentDBService().getExperimentResultData(experiment_name, interval_start_time, interval_end_time); - } catch (Exception e) { sendErrorResponse(response, e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); return; @@ -164,7 +163,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) if (validationOutputData.isSuccess()) sendSuccessResponse(response, kruizeObject, interval_end_time); else { + sendErrorResponse(response, null, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, validationOutputData.getMessage()); + } } catch (Exception e) { e.printStackTrace(); @@ -172,11 +173,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) experiment_name, interval_start_time, interval_end_time); - sendErrorResponse(response, null, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); return; - } } else { sendErrorResponse(response, null, HttpServletResponse.SC_BAD_REQUEST, AnalyzerErrorConstants.APIErrors.UpdateRecommendationsAPI.DATA_NOT_FOUND); diff --git a/src/main/java/com/autotune/analyzer/services/UpdateResults.java b/src/main/java/com/autotune/analyzer/services/UpdateResults.java index 53d480fd2..a150bae51 100644 --- a/src/main/java/com/autotune/analyzer/services/UpdateResults.java +++ b/src/main/java/com/autotune/analyzer/services/UpdateResults.java @@ -20,13 +20,11 @@ import com.autotune.analyzer.experiment.ExperimentInitiator; import com.autotune.analyzer.kruizeObject.KruizeObject; import com.autotune.analyzer.performanceProfiles.PerformanceProfile; -import com.autotune.analyzer.serviceObjects.Converters; import com.autotune.analyzer.serviceObjects.UpdateResultsAPIObject; import com.autotune.analyzer.utils.AnalyzerConstants; import com.autotune.analyzer.utils.AnalyzerErrorConstants; -import com.autotune.common.data.ValidationOutputData; import com.autotune.common.data.result.ExperimentResultData; -import com.autotune.database.service.ExperimentDBService; +import com.autotune.operator.KruizeDeploymentInfo; import com.autotune.utils.MetricsConfig; import com.google.gson.Gson; import io.micrometer.core.instrument.Timer; @@ -41,7 +39,10 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @@ -55,55 +56,43 @@ public class UpdateResults extends HttpServlet { private static final long serialVersionUID = 1L; private static final Logger LOGGER = LoggerFactory.getLogger(UpdateResults.class); - private Map performanceProfilesMap = new HashMap<>(); + public static ConcurrentHashMap performanceProfilesMap; @Override public void init(ServletConfig config) throws ServletException { super.init(config); - this.performanceProfilesMap = (HashMap) getServletContext() - .getAttribute(AnalyzerConstants.PerformanceProfileConstants.PERF_PROFILE_MAP); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Timer.Sample timerUpdateResults = Timer.start(MetricsConfig.meterRegistry()); Map mKruizeExperimentMap = new ConcurrentHashMap(); - ; try { + performanceProfilesMap = (ConcurrentHashMap) getServletContext() + .getAttribute(AnalyzerConstants.PerformanceProfileConstants.PERF_PROFILE_MAP); String inputData = request.getReader().lines().collect(Collectors.joining()); List experimentResultDataList = new ArrayList<>(); List updateResultsAPIObjects = Arrays.asList(new Gson().fromJson(inputData, UpdateResultsAPIObject[].class)); // check for bulk entries and respond accordingly - if (updateResultsAPIObjects.size() > 1) { - LOGGER.error(AnalyzerErrorConstants.AutotuneObjectErrors.UNSUPPORTED_EXPERIMENT); - sendErrorResponse(response, null, HttpServletResponse.SC_BAD_REQUEST, AnalyzerErrorConstants.AutotuneObjectErrors.UNSUPPORTED_EXPERIMENT); + if (updateResultsAPIObjects.size() > KruizeDeploymentInfo.bulk_update_results_limit) { + LOGGER.error(AnalyzerErrorConstants.AutotuneObjectErrors.UNSUPPORTED_EXPERIMENT_RESULTS); + sendErrorResponse(request, response, null, HttpServletResponse.SC_BAD_REQUEST, AnalyzerErrorConstants.AutotuneObjectErrors.UNSUPPORTED_EXPERIMENT_RESULTS); + return; + } + ExperimentInitiator experimentInitiator = new ExperimentInitiator(); + experimentInitiator.validateAndAddExperimentResults(updateResultsAPIObjects); + List failureAPIObjs = experimentInitiator.getFailedUpdateResultsAPIObjects(); + if (failureAPIObjs.size() > 0) { + request.setAttribute("data", failureAPIObjs); + String errorMessage = String.format("Out of a total of %s records, %s failed to save", updateResultsAPIObjects.size(), failureAPIObjs.size()); + sendErrorResponse(request, response, null, HttpServletResponse.SC_BAD_REQUEST, errorMessage); } else { - for (UpdateResultsAPIObject updateResultsAPIObject : updateResultsAPIObjects) { - experimentResultDataList.add(Converters.KruizeObjectConverters.convertUpdateResultsAPIObjToExperimentResultData(updateResultsAPIObject)); - } - ExperimentInitiator experimentInitiator = new ExperimentInitiator(); - ValidationOutputData validationOutputData = experimentInitiator.validateAndUpdateResults(mKruizeExperimentMap, experimentResultDataList, performanceProfilesMap); - ExperimentResultData invalidKExperimentResultData = experimentResultDataList.stream().filter((rData) -> (!rData.getValidationOutputData().isSuccess())).findAny().orElse(null); - ValidationOutputData addedToDB = new ValidationOutputData(false, null, null); - if (null == invalidKExperimentResultData) { - // TODO savetoDB should move to queue and bulk upload not considered here - for (ExperimentResultData resultData : experimentResultDataList) { - addedToDB = new ExperimentDBService().addResultsToDB(resultData); - if (addedToDB.isSuccess()) { - sendSuccessResponse(response, AnalyzerConstants.ServiceConstants.RESULT_SAVED); - } else { - sendErrorResponse(response, null, HttpServletResponse.SC_BAD_REQUEST, addedToDB.getMessage()); - } - } - } else { - LOGGER.error("Failed to update results: " + invalidKExperimentResultData.getValidationOutputData().getMessage()); - sendErrorResponse(response, null, invalidKExperimentResultData.getValidationOutputData().getErrorCode(), invalidKExperimentResultData.getValidationOutputData().getMessage()); - } + sendSuccessResponse(response, AnalyzerConstants.ServiceConstants.RESULT_SAVED); } } catch (Exception e) { LOGGER.error("Exception: " + e.getMessage()); e.printStackTrace(); - sendErrorResponse(response, e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); + sendErrorResponse(request, response, e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } finally { if (null != timerUpdateResults) timerUpdateResults.stop(MetricsConfig.timerUpdateResults); } @@ -122,7 +111,7 @@ private void sendSuccessResponse(HttpServletResponse response, String message) t out.flush(); } - public void sendErrorResponse(HttpServletResponse response, Exception e, int httpStatusCode, String errorMsg) throws + public void sendErrorResponse(HttpServletRequest request, HttpServletResponse response, Exception e, int httpStatusCode, String errorMsg) throws IOException { if (null != e) { LOGGER.error(e.toString()); diff --git a/src/main/java/com/autotune/analyzer/utils/AnalyzerErrorConstants.java b/src/main/java/com/autotune/analyzer/utils/AnalyzerErrorConstants.java index 9cfb57329..7c145bade 100644 --- a/src/main/java/com/autotune/analyzer/utils/AnalyzerErrorConstants.java +++ b/src/main/java/com/autotune/analyzer/utils/AnalyzerErrorConstants.java @@ -73,9 +73,12 @@ public static final class AutotuneObjectErrors { public static final String SLO_REDUNDANCY_ERROR = "SLO Data and Performance Profile cannot exist simultaneously!"; public static final String DUPLICATE_PERF_PROFILE = "Performance Profile already exists: "; public static final String MISSING_PERF_PROFILE = "Performance Profile doesn't exist : "; - public static final String UNSUPPORTED_EXPERIMENT = "Bulk entries are currently unsupported!"; + public static final String UNSUPPORTED_EXPERIMENT = String.format("At present, the system does not support bulk entries!"); + public static final String UNSUPPORTED_EXPERIMENT_RESULTS = String.format("At present, the system does not support bulk entries exceeding %s in quantity!", KruizeDeploymentInfo.bulk_update_results_limit); + public static final String UNSUPPORTED_BULK_KUBERNETES = "Bulk Kubernetes objects are currently unsupported!"; public static final String DUPLICATE_EXPERIMENT = "Experiment name already exists: "; - public static final String WRONG_TIMESTAMP = "EndTimeStamp cannot be less than StartTimeStamp!"; + public static final String WRONG_TIMESTAMP = "The Start time should precede the End time!"; + public static final String MEASUREMENT_DURATION_ERROR = "Interval duration cannot be less than or greater than measurement_duration by more than " + KruizeConstants.TimeConv.MEASUREMENT_DURATION_THRESHOLD_SECONDS + " seconds"; private AutotuneObjectErrors() { @@ -129,9 +132,7 @@ public static final class UpdateRecommendationsAPI { public static final String EXPERIMENT_NAME_MANDATORY = KruizeConstants.JSONKeys.EXPERIMENT_NAME + " is mandatory"; public static final String INTERVAL_END_TIME_MANDATORY = KruizeConstants.JSONKeys.INTERVAL_END_TIME + " is mandatory"; public static final String DATA_NOT_FOUND = "Data not found!"; - public static final String TIME_COMPARE = "The Start time should precede the End time!"; - public static final String TIME_GAP_LIMIT = String.format("The gap between the interval_start_time and interval_end_time must be within a maximum of %s days!", KruizeDeploymentInfo.generate_recommendations_date_range_limit_in_days); private UpdateRecommendationsAPI() { diff --git a/src/main/java/com/autotune/common/data/metrics/MetricPercentileResults.java b/src/main/java/com/autotune/common/data/metrics/MetricPercentileResults.java index b66c91e8e..51b44a60a 100644 --- a/src/main/java/com/autotune/common/data/metrics/MetricPercentileResults.java +++ b/src/main/java/com/autotune/common/data/metrics/MetricPercentileResults.java @@ -4,15 +4,15 @@ import org.json.JSONObject; public class MetricPercentileResults { - private float percentile50; - private float percentile97; - private float percentile95; - private float percentile99; - private float percentile99Point9; - private float percentile99Point99; - private float percentile99Point999; - private float percentile99Point9999; - private float percentile100; + private Float percentile50; + private Float percentile97; + private Float percentile95; + private Float percentile99; + private Float percentile99Point9; + private Float percentile99Point99; + private Float percentile99Point999; + private Float percentile99Point9999; + private Float percentile100; public MetricPercentileResults() { } diff --git a/src/main/java/com/autotune/database/dao/ExperimentDAO.java b/src/main/java/com/autotune/database/dao/ExperimentDAO.java index 284d24bca..558ea6710 100644 --- a/src/main/java/com/autotune/database/dao/ExperimentDAO.java +++ b/src/main/java/com/autotune/database/dao/ExperimentDAO.java @@ -19,6 +19,8 @@ public interface ExperimentDAO { // Add experiment results from local storage to DB and set status to Inprogress public ValidationOutputData addResultsToDB(KruizeResultsEntry resultsEntry); + public List addToDBAndFetchFailedResults(List kruizeResultsEntries); + // Add recommendation to DB public ValidationOutputData addRecommendationToDB(KruizeRecommendationEntry recommendationEntry); @@ -47,14 +49,17 @@ public interface ExperimentDAO { List loadExperimentByName(String experimentName) throws Exception; // Load all results for a particular experimentName + List loadResultsByExperimentName(String experimentName, Timestamp interval_start_time, Integer limitRows) throws Exception; // Load all recommendations of a particular experiment List loadRecommendationsByExperimentName(String experimentName) throws Exception; + // Load a single Performance Profile based on name List loadPerformanceProfileByName(String performanceProfileName) throws Exception; + // Load all recommendations of a particular experiment and interval end Time KruizeRecommendationEntry loadRecommendationsByExperimentNameAndDate(String experimentName, Timestamp interval_end_time) throws Exception; diff --git a/src/main/java/com/autotune/database/dao/ExperimentDAOImpl.java b/src/main/java/com/autotune/database/dao/ExperimentDAOImpl.java index 7ee8a6342..51dc6e84c 100644 --- a/src/main/java/com/autotune/database/dao/ExperimentDAOImpl.java +++ b/src/main/java/com/autotune/database/dao/ExperimentDAOImpl.java @@ -22,8 +22,10 @@ import org.slf4j.LoggerFactory; import java.sql.Timestamp; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import static com.autotune.database.helper.DBConstants.SQLQUERY.*; @@ -76,9 +78,7 @@ public ValidationOutputData addResultsToDB(KruizeResultsEntry resultsEntry) { validationOutputData.setMessage( String.format("A record with the name %s already exists within the timestamp range starting from %s and ending on %s.", resultsEntry.getExperiment_name(), resultsEntry.getInterval_start_time(), resultsEntry.getInterval_end_time()) ); - } else { - throw new Exception(ex.getMessage()); } } catch (Exception e) { @@ -97,6 +97,49 @@ public ValidationOutputData addResultsToDB(KruizeResultsEntry resultsEntry) { return validationOutputData; } + @Override + public List addToDBAndFetchFailedResults(List kruizeResultsEntries) { + List failedResultsEntries = new ArrayList<>(); + Transaction tx = null; + Timer.Sample timerAddResultsDB = Timer.start(MetricsConfig.meterRegistry()); + try (Session session = KruizeHibernateUtil.getSessionFactory().openSession()) { + tx = session.beginTransaction(); + for (KruizeResultsEntry entry : kruizeResultsEntries) { + try { + session.merge(entry); + } catch (PersistenceException e) { + entry.setErrorReasons(List.of(String.format("A record with the name %s already exists within the timestamp range starting from %s and ending on %s.", entry.getExperiment_name(), new SimpleDateFormat(KruizeConstants.DateFormats.STANDARD_JSON_DATE_FORMAT).format(entry.getInterval_start_time()), + new SimpleDateFormat(KruizeConstants.DateFormats.STANDARD_JSON_DATE_FORMAT).format(entry.getInterval_end_time())))); + failedResultsEntries.add(entry); + } catch (Exception e) { + entry.setErrorReasons(List.of(e.getMessage())); + failedResultsEntries.add(entry); + } + } + tx.commit(); + if (!failedResultsEntries.isEmpty()) { + // find elements in kruizeResultsEntries but not in failedResultsEntries + List elementsInSuccessOnly = kruizeResultsEntries.stream() + .filter(entry -> !failedResultsEntries.contains(entry)) + .collect(Collectors.toList()); + tx = session.beginTransaction(); + for (KruizeResultsEntry entry : elementsInSuccessOnly) { + session.merge(entry); + } + tx.commit(); + } + } catch (Exception e) { + LOGGER.error("Not able to save experiment due to {}", e.getMessage()); + failedResultsEntries.addAll(kruizeResultsEntries); + failedResultsEntries.forEach((entry) -> { + entry.setErrorReasons(List.of(e.getMessage())); + }); + } finally { + if (null != timerAddResultsDB) timerAddResultsDB.stop(MetricsConfig.timerAddResultsDB); + } + return failedResultsEntries; + } + @Override public ValidationOutputData addRecommendationToDB(KruizeRecommendationEntry recommendationEntry) { ValidationOutputData validationOutputData = new ValidationOutputData(false, null, null); @@ -335,18 +378,6 @@ public List loadRecommendationsByExperimentName(Strin } @Override - public List loadPerformanceProfileByName(String performanceProfileName) throws Exception { - List entries = null; - try (Session session = KruizeHibernateUtil.getSessionFactory().openSession()) { - entries = session.createQuery(DBConstants.SQLQUERY.SELECT_FROM_PERFORMANCE_PROFILE_BY_NAME, KruizePerformanceProfileEntry.class) - .setParameter("name", performanceProfileName).list(); - } catch (Exception e) { - LOGGER.error("Not able to load Performance Profile {} due to {}", performanceProfileName, e.getMessage()); - throw new Exception("Error while loading existing profile from database due to : " + e.getMessage()); - } - return entries; - } - public KruizeRecommendationEntry loadRecommendationsByExperimentNameAndDate(String experimentName, Timestamp interval_end_time) throws Exception { KruizeRecommendationEntry recommendationEntries = null; Timer.Sample timerLoadRecExpName = Timer.start(MetricsConfig.meterRegistry()); @@ -367,6 +398,20 @@ public KruizeRecommendationEntry loadRecommendationsByExperimentNameAndDate(Stri return recommendationEntries; } + + public List loadPerformanceProfileByName(String performanceProfileName) throws Exception { + List entries = null; + try (Session session = KruizeHibernateUtil.getSessionFactory().openSession()) { + entries = session.createQuery(DBConstants.SQLQUERY.SELECT_FROM_PERFORMANCE_PROFILE_BY_NAME, KruizePerformanceProfileEntry.class) + .setParameter("name", performanceProfileName).list(); + } catch (Exception e) { + LOGGER.error("Not able to load Performance Profile {} due to {}", performanceProfileName, e.getMessage()); + throw new Exception("Error while loading existing profile from database due to : " + e.getMessage()); + } + return entries; + } + + @Override public List getKruizeResultsEntry(String experiment_name, Timestamp interval_start_time, Timestamp interval_end_time) throws Exception { List kruizeResultsEntryList = new ArrayList<>(); @@ -388,6 +433,8 @@ public List getKruizeResultsEntry(String experiment_name, Ti .setParameter(KruizeConstants.JSONKeys.EXPERIMENT_NAME, experiment_name) .getResultList(); } + + } catch (NoResultException e) { LOGGER.error("Data not found in kruizeResultsEntry for exp_name:{} interval_end_time:{} ", experiment_name, interval_end_time); kruizeResultsEntryList = null; @@ -397,5 +444,6 @@ public List getKruizeResultsEntry(String experiment_name, Ti throw new Exception("Error while loading results from the database due to : " + e.getMessage()); } return kruizeResultsEntryList; + } } diff --git a/src/main/java/com/autotune/database/helper/DBConstants.java b/src/main/java/com/autotune/database/helper/DBConstants.java index 0ac9a4a95..a949b21e2 100644 --- a/src/main/java/com/autotune/database/helper/DBConstants.java +++ b/src/main/java/com/autotune/database/helper/DBConstants.java @@ -10,12 +10,12 @@ public static final class SQLQUERY { public static final String SELECT_FROM_RESULTS = "from KruizeResultsEntry"; public static final String SELECT_FROM_RESULTS_BY_EXP_NAME = "from KruizeResultsEntry k WHERE k.experiment_name = :experimentName"; public static final String SELECT_FROM_RESULTS_BY_EXP_NAME_AND_DATE_RANGE = String.format("from KruizeResultsEntry k WHERE k.experiment_name = :%s and k.interval_end_time <= :%s ORDER BY k.interval_end_time DESC", KruizeConstants.JSONKeys.EXPERIMENT_NAME, KruizeConstants.JSONKeys.INTERVAL_END_TIME, KruizeConstants.JSONKeys.INTERVAL_START_TIME); + public static final String SELECT_FROM_RESULTS_BY_EXP_NAME_AND_START_END_TIME = String.format("from KruizeResultsEntry k WHERE k.experiment_name = :%s and k.interval_start_time >= :%s and k.interval_end_time <= :%s", KruizeConstants.JSONKeys.EXPERIMENT_NAME, KruizeConstants.JSONKeys.INTERVAL_START_TIME, KruizeConstants.JSONKeys.INTERVAL_END_TIME); public static final String SELECT_FROM_RESULTS_BY_EXP_NAME_AND_END_TIME = String.format("from KruizeResultsEntry k WHERE k.experiment_name = :%s and k.interval_end_time = :%s", KruizeConstants.JSONKeys.EXPERIMENT_NAME, KruizeConstants.JSONKeys.INTERVAL_END_TIME); public static final String SELECT_FROM_RESULTS_BY_EXP_NAME_AND_MAX_END_TIME = String.format("from KruizeResultsEntry k WHERE k.experiment_name = :%s and k.interval_end_time = (SELECT MAX(e.interval_end_time) FROM KruizeResultsEntry e where e.experiment_name = :%s )", KruizeConstants.JSONKeys.EXPERIMENT_NAME, KruizeConstants.JSONKeys.EXPERIMENT_NAME); public static final String SELECT_FROM_RECOMMENDATIONS_BY_EXP_NAME = "from KruizeRecommendationEntry k WHERE k.experiment_name = :experimentName"; public static final String SELECT_FROM_RECOMMENDATIONS_BY_EXP_NAME_AND_END_TIME = String.format("from KruizeRecommendationEntry k WHERE k.experiment_name = :%s and k.interval_end_time= :%s", KruizeConstants.JSONKeys.EXPERIMENT_NAME, KruizeConstants.JSONKeys.INTERVAL_END_TIME); public static final String SELECT_FROM_RECOMMENDATIONS = "from KruizeRecommendationEntry"; - public static final String SELECT_FROM_RESULTS_BY_EXP_NAME_AND_START_END_TIME = String.format("from KruizeResultsEntry k WHERE k.experiment_name = :%s and k.interval_start_time >= :%s and k.interval_end_time <= :%s", KruizeConstants.JSONKeys.EXPERIMENT_NAME, KruizeConstants.JSONKeys.INTERVAL_START_TIME, KruizeConstants.JSONKeys.INTERVAL_END_TIME); public static final String SELECT_FROM_PERFORMANCE_PROFILE = "from KruizePerformanceProfileEntry"; public static final String SELECT_FROM_PERFORMANCE_PROFILE_BY_NAME = "from KruizePerformanceProfileEntry k WHERE k.name = :name"; public static final String DELETE_FROM_EXPERIMENTS_BY_EXP_NAME = "DELETE FROM KruizeExperimentEntry k WHERE k.experiment_name = :experimentName"; diff --git a/src/main/java/com/autotune/database/helper/DBHelpers.java b/src/main/java/com/autotune/database/helper/DBHelpers.java index c80c223da..cb7c2c3b6 100644 --- a/src/main/java/com/autotune/database/helper/DBHelpers.java +++ b/src/main/java/com/autotune/database/helper/DBHelpers.java @@ -491,6 +491,7 @@ public static List convertResultEntryToUpdateResultsAPIO updateResultsAPIObject.setExperimentName(kruizeResultsEntry.getExperiment_name()); updateResultsAPIObject.setStartTimestamp(kruizeResultsEntry.getInterval_start_time()); updateResultsAPIObject.setEndTimestamp(kruizeResultsEntry.getInterval_end_time()); + updateResultsAPIObject.setErrorReasons(kruizeResultsEntry.getErrorReasons()); JsonNode extendedDataNode = kruizeResultsEntry.getExtended_data(); JsonNode k8sObjectsNode = extendedDataNode.get(KruizeConstants.JSONKeys.KUBERNETES_OBJECTS); List k8sObjectList = new ArrayList<>(); diff --git a/src/main/java/com/autotune/database/service/ExperimentDBService.java b/src/main/java/com/autotune/database/service/ExperimentDBService.java index 3898abb42..c394ac70c 100644 --- a/src/main/java/com/autotune/database/service/ExperimentDBService.java +++ b/src/main/java/com/autotune/database/service/ExperimentDBService.java @@ -41,6 +41,7 @@ import java.sql.Timestamp; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -129,19 +130,19 @@ public void loadAllRecommendations(Map mainKruizeExperimen } } - public void loadAllPerformanceProfiles(Map performanceProfileMap) throws Exception { - List entries = experimentDAO.loadAllPerformanceProfiles(); - if (null != entries && !entries.isEmpty()) { - List performanceProfiles = DBHelpers.Converters.KruizeObjectConverters.convertPerformanceProfileEntryToPerformanceProfileObject(entries); - if (!performanceProfiles.isEmpty()) { - performanceProfiles.forEach(performanceProfile -> - PerformanceProfileUtil.addPerformanceProfile(performanceProfileMap, performanceProfile)); + if (performanceProfileMap.isEmpty()) { + List entries = experimentDAO.loadAllPerformanceProfiles(); + if (null != entries && !entries.isEmpty()) { + List performanceProfiles = DBHelpers.Converters.KruizeObjectConverters.convertPerformanceProfileEntryToPerformanceProfileObject(entries); + if (!performanceProfiles.isEmpty()) { + performanceProfiles.forEach(performanceProfile -> + PerformanceProfileUtil.addPerformanceProfile(performanceProfileMap, performanceProfile)); + } } } } - public void loadResultsFromDBByName(Map mainKruizeExperimentMap, String experimentName, Timestamp interval_end_time, Integer limitRows) throws Exception { ExperimentInterface experimentInterface = new ExperimentInterfaceImpl(); // Load results from the DB and save to local @@ -201,18 +202,19 @@ public ValidationOutputData addExperimentToDB(CreateExperimentAPIObject createEx return validationOutputData; } - public ValidationOutputData addResultsToDB(ExperimentResultData resultData) { - ValidationOutputData validationOutputData = new ValidationOutputData(false, null, null); - KruizeResultsEntry kruizeResultsEntry = DBHelpers.Converters.KruizeObjectConverters.convertExperimentResultToExperimentResultsTable(resultData); - validationOutputData = experimentDAO.addResultsToDB(kruizeResultsEntry); - if (validationOutputData.isSuccess()) - resultData.setStatus(AnalyzerConstants.ExperimentStatus.IN_PROGRESS); - else { - resultData.setStatus(AnalyzerConstants.ExperimentStatus.FAILED); + public List addResultsToDB(List resultDataList) { + List kruizeResultsEntryList = new ArrayList<>(); + List failedUpdateResultsAPIObjects = new ArrayList<>(); + for (ExperimentResultData resultData : resultDataList) { + KruizeResultsEntry kruizeResultsEntry = DBHelpers.Converters.KruizeObjectConverters.convertExperimentResultToExperimentResultsTable(resultData); + kruizeResultsEntryList.add(kruizeResultsEntry); } - return validationOutputData; + List failedResultsEntries = experimentDAO.addToDBAndFetchFailedResults(kruizeResultsEntryList); + failedUpdateResultsAPIObjects = DBHelpers.Converters.KruizeObjectConverters.convertResultEntryToUpdateResultsAPIObject(failedResultsEntries); + return failedUpdateResultsAPIObjects; } + public ValidationOutputData addRecommendationToDB(Map experimentsMap, List experimentResultDataList) { ValidationOutputData validationOutputData = new ValidationOutputData(false, "", null); if (null == experimentResultDataList) { @@ -221,7 +223,6 @@ public ValidationOutputData addRecommendationToDB(Map expe if (experimentResultDataList.size() == 0) { return validationOutputData; } - for (ExperimentResultData experimentResultData : experimentResultDataList) { // TODO: Log the list of invalid experiments and return the error instead of bailing out completely if (!experimentsMap.containsKey(experimentResultData.getExperiment_name())) { @@ -259,7 +260,6 @@ public ValidationOutputData addPerformanceProfileToDB(PerformanceProfile perform return validationOutputData; } - /* * This is a Java method that loads all experiments from the database using an experimentDAO object. * The method then converts the retrieved data into KruizeObject format, adds them to a list, @@ -344,6 +344,7 @@ public boolean updateExperimentStatus(KruizeObject kruizeObject, AnalyzerConstan return true; } + public List getExperimentResultData(String experiment_name, Timestamp interval_start_time, Timestamp interval_end_time) throws Exception { List experimentResultDataList = new ArrayList<>(); List kruizeResultsEntryList = experimentDAO.getKruizeResultsEntry(experiment_name, interval_start_time, interval_end_time); diff --git a/src/main/java/com/autotune/database/table/KruizeResultsEntry.java b/src/main/java/com/autotune/database/table/KruizeResultsEntry.java index 0f443df35..4fd5c0a60 100644 --- a/src/main/java/com/autotune/database/table/KruizeResultsEntry.java +++ b/src/main/java/com/autotune/database/table/KruizeResultsEntry.java @@ -21,6 +21,7 @@ import org.hibernate.type.SqlTypes; import java.sql.Timestamp; +import java.util.List; /** * This is a Java class named KruizeResultsEntry annotated with JPA annotations. @@ -47,6 +48,9 @@ public class KruizeResultsEntry { @JdbcTypeCode(SqlTypes.JSON) private JsonNode meta_data; + @Transient + private List errorReasons; + public String getExperiment_name() { return experiment_name; } @@ -102,4 +106,12 @@ public String getVersion() { public void setVersion(String version) { this.version = version; } + + public List getErrorReasons() { + return errorReasons; + } + + public void setErrorReasons(List errorReasons) { + this.errorReasons = errorReasons; + } } diff --git a/src/main/java/com/autotune/operator/InitializeDeployment.java b/src/main/java/com/autotune/operator/InitializeDeployment.java index b50698407..8dd60dd92 100644 --- a/src/main/java/com/autotune/operator/InitializeDeployment.java +++ b/src/main/java/com/autotune/operator/InitializeDeployment.java @@ -105,7 +105,10 @@ private static void setConfigValues(String configFileName, Class envClass) { deploymentInfoField.set(null, deploymentInfoFieldValue); else if (deploymentInfoField.getType() == Boolean.class) deploymentInfoField.set(null, Boolean.parseBoolean(deploymentInfoFieldValue)); - else + else if (deploymentInfoField.getType() == Integer.class) { + assert deploymentInfoFieldValue != null; + deploymentInfoField.set(null, Integer.parseInt(deploymentInfoFieldValue)); + } else throw new IllegalAccessException("Failed to set " + deploymentInfoField + "due to its type " + deploymentInfoField.getType()); } catch (Exception e) { LOGGER.warn("Error while setting config variables : {} : {}", e.getClass(), e.getMessage()); @@ -137,7 +140,7 @@ private static String getKruizeConfigValue(String envName, JSONObject kruizeConf //Override if env value set outside kruizeConfigJson String sysEnvValue = System.getenv(envName); if (null == sysEnvValue && null == envValue) { - message = message + ", nor not able to set via environment variable and set to null."; + message = message + ", nor not able to set via environment variable and set to null or default."; } else { if (null != sysEnvValue && envValue == null) { envValue = sysEnvValue; diff --git a/src/main/java/com/autotune/operator/KruizeDeploymentInfo.java b/src/main/java/com/autotune/operator/KruizeDeploymentInfo.java index e1e7b5217..c13cdafba 100644 --- a/src/main/java/com/autotune/operator/KruizeDeploymentInfo.java +++ b/src/main/java/com/autotune/operator/KruizeDeploymentInfo.java @@ -65,6 +65,7 @@ public class KruizeDeploymentInfo { public static String database_ssl_mode; public static Boolean settings_save_to_db; public static String em_only_mode; + public static Integer bulk_update_results_limit = 100; public static int generate_recommendations_date_range_limit_in_days = 15; diff --git a/src/main/java/com/autotune/service/InitiateListener.java b/src/main/java/com/autotune/service/InitiateListener.java index 1de693c0b..9fff47d27 100644 --- a/src/main/java/com/autotune/service/InitiateListener.java +++ b/src/main/java/com/autotune/service/InitiateListener.java @@ -15,11 +15,12 @@ *******************************************************************************/ package com.autotune.service; -import com.autotune.analyzer.performanceProfiles.PerformanceProfilesDeployment; +import com.autotune.analyzer.performanceProfiles.PerformanceProfile; import com.autotune.analyzer.utils.AnalyzerConstants; import com.autotune.common.parallelengine.executor.KruizeExecutor; import com.autotune.common.parallelengine.queue.KruizeQueue; import com.autotune.common.trials.ExperimentTrial; +import com.autotune.database.service.ExperimentDBService; import com.autotune.experimentManager.data.ExperimentDetailsMap; import com.autotune.experimentManager.utils.EMConstants; import com.autotune.experimentManager.utils.EMConstants.ParallelEngineConfigs; @@ -31,6 +32,7 @@ import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -147,7 +149,13 @@ public void run() { /* Kruize Performance Profile configuration */ - sce.getServletContext().setAttribute(AnalyzerConstants.PerformanceProfileConstants.PERF_PROFILE_MAP, PerformanceProfilesDeployment.performanceProfilesMap); + ConcurrentHashMap performanceProfilesMap = new ConcurrentHashMap<>(); + try { + new ExperimentDBService().loadAllPerformanceProfiles(performanceProfilesMap); + } catch (Exception e) { + LOGGER.error("Failed to load performance profile: {} ", e.getMessage()); + } + sce.getServletContext().setAttribute(AnalyzerConstants.PerformanceProfileConstants.PERF_PROFILE_MAP, performanceProfilesMap); } @Override diff --git a/src/main/java/com/autotune/utils/KruizeConstants.java b/src/main/java/com/autotune/utils/KruizeConstants.java index cdb744629..dbd6c7920 100644 --- a/src/main/java/com/autotune/utils/KruizeConstants.java +++ b/src/main/java/com/autotune/utils/KruizeConstants.java @@ -244,10 +244,10 @@ private TimeUnitsExt() { public static class TimeConv { public static final int NO_OF_MSECS_IN_SEC = 1000; + public static final int MEASUREMENT_DURATION_THRESHOLD_SECONDS = 30; public static int NO_OF_SECONDS_PER_MINUTE = 60; public static int NO_OF_MINUTES_PER_HOUR = 60; public static int NO_OF_HOURS_PER_DAY = 24; - public static int MEASUREMENT_DURATION_THRESHOLD_SECONDS = 30; private TimeConv() { } @@ -377,6 +377,7 @@ private DateFormats() { * In order to assign values to the static variables of KruizeDeploymentInfo * using Java reflection, the class variables are utilized, and therefore, * if any new variables are added, their corresponding declaration is necessary. + * Ref InitializeDeployment.setConfigValues(KruizeConstants.CONFIG_FILE, KruizeConstants.DATABASE_ENV_NAME.class); */ public static final class DATABASE_ENV_NAME { public static final String DATABASE_ADMIN_USERNAME = "database_adminusername"; @@ -393,6 +394,7 @@ public static final class DATABASE_ENV_NAME { * In order to assign values to the static variables of KruizeDeploymentInfo * using Java reflection, the class variables are utilized, and therefore, * if any new variables are added, their corresponding declaration is necessary. + * Ref InitializeDeployment.setConfigValues(KruizeConstants.CONFIG_FILE, KruizeConstants.KRUIZE_CONFIG_ENV_NAME.class); */ public static final class KRUIZE_CONFIG_ENV_NAME { public static final String K8S_TYPE = "k8stype"; @@ -404,6 +406,7 @@ public static final class KRUIZE_CONFIG_ENV_NAME { public static final String CLUSTER_TYPE = "clustertype"; public static final String AUTOTUNE_MODE = "autotunemode"; public static final String EM_ONLY_MODE = "emonly"; + public static final String BULK_UPDATE_RESULTS_LIMIT = "bulkresultslimit"; public static final String SETTINGS_SAVE_TO_DB = "savetodb"; public static final String SETTINGS_DB_DRIVER = "dbdriver"; public static final String SETTINGS_HIBERNATE_DIALECT = "hibernate_dialect"; diff --git a/src/main/java/com/autotune/utils/ServerContext.java b/src/main/java/com/autotune/utils/ServerContext.java index 63ddf842a..a06ed3c40 100644 --- a/src/main/java/com/autotune/utils/ServerContext.java +++ b/src/main/java/com/autotune/utils/ServerContext.java @@ -22,6 +22,7 @@ */ public class ServerContext { public static final int KRUIZE_SERVER_PORT = Integer.parseInt(System.getenv().getOrDefault("AUTOTUNE_SERVER_PORT", "8080")); + public static final int KRUIZE_HTTP_THREAD_POOL_COUNT = Integer.parseInt(System.getenv().getOrDefault("KRUIZE_HTTP_THREAD_POOL_COUNT", "6")); public static final int HPO_SERVER_PORT = 8085; // AnalyzerConstants end points diff --git a/src/main/java/com/autotune/utils/Utils.java b/src/main/java/com/autotune/utils/Utils.java index f0e493460..2d5e232f7 100644 --- a/src/main/java/com/autotune/utils/Utils.java +++ b/src/main/java/com/autotune/utils/Utils.java @@ -237,6 +237,7 @@ public static Timestamp getTimeStampFrom(String format, String date) { Timestamp convertedDate = Timestamp.from(desiredInstant); return convertedDate; + } catch (Exception e) { return null; }