diff --git a/src/main/java/com/autotune/analyzer/Analyzer.java b/src/main/java/com/autotune/analyzer/Analyzer.java index 12a0ab262..f06d5b933 100644 --- a/src/main/java/com/autotune/analyzer/Analyzer.java +++ b/src/main/java/com/autotune/analyzer/Analyzer.java @@ -56,6 +56,8 @@ public static void addServlets(ServletContextHandler context) { context.addServlet(MetricProfileService.class, ServerContext.CREATE_METRIC_PROFILE); context.addServlet(MetricProfileService.class, ServerContext.LIST_METRIC_PROFILES); context.addServlet(MetricProfileService.class, ServerContext.DELETE_METRIC_PROFILE); + context.addServlet(MetadataProfileService.class, ServerContext.CREATE_METADATA_PROFILE); + context.addServlet(MetadataProfileService.class, ServerContext.LIST_METADATA_PROFILES); context.addServlet(ListDatasources.class, ServerContext.LIST_DATASOURCES); context.addServlet(DSMetadataService.class, ServerContext.DATASOURCE_METADATA); context.addServlet(BulkService.class, ServerContext.BULK_SERVICE); diff --git a/src/main/java/com/autotune/analyzer/exceptions/MetadataProfileResponse.java b/src/main/java/com/autotune/analyzer/exceptions/MetadataProfileResponse.java new file mode 100644 index 000000000..cd130982a --- /dev/null +++ b/src/main/java/com/autotune/analyzer/exceptions/MetadataProfileResponse.java @@ -0,0 +1,47 @@ +package com.autotune.analyzer.exceptions; + +public class MetadataProfileResponse { + private String message; + private int httpcode; + private String documentationLink; + private String status; + + public MetadataProfileResponse(String message, int httpcode, String documentationLink, String status) { + this.message = message; + this.httpcode = httpcode; + this.documentationLink = documentationLink; + this.status = status; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public int getHttpcode() { + return httpcode; + } + + public void setHttpcode(int httpcode) { + this.httpcode = httpcode; + } + + public String getDocumentationLink() { + return documentationLink; + } + + public void setDocumentationLink(String documentationLink) { + this.documentationLink = documentationLink; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } +} diff --git a/src/main/java/com/autotune/analyzer/metadataProfiles/MetadataProfileCollection.java b/src/main/java/com/autotune/analyzer/metadataProfiles/MetadataProfileCollection.java new file mode 100644 index 000000000..4061f00c5 --- /dev/null +++ b/src/main/java/com/autotune/analyzer/metadataProfiles/MetadataProfileCollection.java @@ -0,0 +1,60 @@ +package com.autotune.analyzer.metadataProfiles; + +import com.autotune.database.service.ExperimentDBService; +import com.autotune.utils.KruizeConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + +public class MetadataProfileCollection { + private static final Logger LOGGER = LoggerFactory.getLogger(MetadataProfileCollection.class); + private static MetadataProfileCollection metadataProfileCollectionInstance = new MetadataProfileCollection(); + private HashMap metadataProfileCollection; + + private MetadataProfileCollection() { + this.metadataProfileCollection = new HashMap<>(); + } + + public static MetadataProfileCollection getInstance() { + return metadataProfileCollectionInstance; + } + + public HashMap getMetadataProfileCollection() { + return metadataProfileCollection; + } + + public void loadMetadataProfilesFromDB() { + try { + LOGGER.info(KruizeConstants.MetadataProfileConstants.CHECKING_AVAILABLE_METADATA_PROFILE_FROM_DB); + Map availableMetadataProfiles = new HashMap<>(); + new ExperimentDBService().loadAllMetadataProfiles(availableMetadataProfiles); + if (availableMetadataProfiles.isEmpty()) { + LOGGER.info(KruizeConstants.MetadataProfileConstants.NO_METADATA_PROFILE_FOUND_IN_DB); + }else { + for (Map.Entry metadataProfile : availableMetadataProfiles.entrySet()) { + LOGGER.info(KruizeConstants.MetadataProfileConstants.METADATA_PROFILE_FOUND, metadataProfile.getKey()); + metadataProfileCollection.put(metadataProfile.getKey(), metadataProfile.getValue()); + } + } + + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + } + + + public void addMetadataProfile(MetadataProfile metadataProfile) { + String metadataProfileName = metadataProfile.getMetadata().get("name").asText(); + + LOGGER.info(KruizeConstants.MetadataProfileConstants.ADDING_METADATA_PROFILE + "{}", metadataProfileName); + + if(metadataProfileCollection.containsKey(metadataProfileName)) { + LOGGER.error(KruizeConstants.MetadataProfileConstants.METADATA_PROFILE_ALREADY_EXISTS + "{}", metadataProfileName); + } else { + LOGGER.info(KruizeConstants.MetadataProfileConstants.METADATA_PROFILE_ADDED + "{}", metadataProfileName); + metadataProfileCollection.put(metadataProfileName, metadataProfile); + } + } +} diff --git a/src/main/java/com/autotune/analyzer/serviceObjects/Converters.java b/src/main/java/com/autotune/analyzer/serviceObjects/Converters.java index ae2e6e9a9..c71f1960c 100644 --- a/src/main/java/com/autotune/analyzer/serviceObjects/Converters.java +++ b/src/main/java/com/autotune/analyzer/serviceObjects/Converters.java @@ -388,6 +388,50 @@ public static PerformanceProfile convertInputJSONToCreateMetricProfile(String in return metricProfile; } + public static MetadataProfile convertInputJSONToCreateMetadataProfile(String inputData) throws InvalidValueException, Exception { + MetadataProfile metadataProfile = null; + if (inputData != null) { + JSONObject jsonObject = new JSONObject(inputData); + String apiVersion = jsonObject.getString(AnalyzerConstants.API_VERSION); + String kind = jsonObject.getString(AnalyzerConstants.KIND); + + JSONObject metadataObject = jsonObject.getJSONObject(AnalyzerConstants.AutotuneObjectConstants.METADATA); + ObjectMapper objectMapper = new ObjectMapper(); + ObjectNode metadata = objectMapper.readValue(metadataObject.toString(), ObjectNode.class); + metadata.put("name", metadataObject.getString("name")); + + Double profileVersion = jsonObject.has(AnalyzerConstants.PROFILE_VERSION) ? jsonObject.getDouble(AnalyzerConstants.PROFILE_VERSION) : null; + String k8sType = jsonObject.has(AnalyzerConstants.MetadataProfileConstants.K8S_TYPE) ? jsonObject.getString(AnalyzerConstants.MetadataProfileConstants.K8S_TYPE) : null; + JSONArray queryVariableArray = jsonObject.getJSONArray(AnalyzerConstants.AutotuneObjectConstants.QUERY_VARIABLES); + ArrayList queryVariablesList = new ArrayList<>(); + for (Object object : queryVariableArray) { + JSONObject functionVarObj = (JSONObject) object; + String name = functionVarObj.getString(AnalyzerConstants.AutotuneObjectConstants.NAME); + String datasource = functionVarObj.getString(AnalyzerConstants.AutotuneObjectConstants.DATASOURCE); + String query = functionVarObj.has(AnalyzerConstants.AutotuneObjectConstants.QUERY) ? functionVarObj.getString(AnalyzerConstants.AutotuneObjectConstants.QUERY) : null; + String valueType = functionVarObj.getString(AnalyzerConstants.AutotuneObjectConstants.VALUE_TYPE); + String kubeObject = functionVarObj.has(AnalyzerConstants.KUBERNETES_OBJECT) ? functionVarObj.getString(AnalyzerConstants.KUBERNETES_OBJECT) : null; + Metric metric = new Metric(name, query, datasource, valueType, kubeObject); + JSONArray aggrFunctionArray = functionVarObj.has(AnalyzerConstants.AGGREGATION_FUNCTIONS) ? functionVarObj.getJSONArray(AnalyzerConstants.AGGREGATION_FUNCTIONS) : null; + HashMap aggregationFunctionsMap = new HashMap<>(); + for (Object innerObject : aggrFunctionArray) { + JSONObject aggrFuncJsonObject = (JSONObject) innerObject; + String function = aggrFuncJsonObject.getString(AnalyzerConstants.FUNCTION); + String aggrFuncQuery = aggrFuncJsonObject.getString(KruizeConstants.JSONKeys.QUERY); + String version = aggrFuncJsonObject.has(KruizeConstants.JSONKeys.VERSION) ? aggrFuncJsonObject.getString(KruizeConstants.JSONKeys.VERSION) : null; + AggregationFunctions aggregationFunctions = new AggregationFunctions(function, aggrFuncQuery, version); + aggregationFunctionsMap.put(function, aggregationFunctions); + } + metric.setAggregationFunctionsMap(aggregationFunctionsMap); + queryVariablesList.add(metric); + } + + metadataProfile = new MetadataProfile(apiVersion, kind, metadata, profileVersion, k8sType, queryVariablesList); + } + return metadataProfile; + } + + public static ConcurrentHashMap ConvertUpdateResultDataToAPIResponse(ConcurrentHashMap mainKruizeExperimentMap) { return null; } diff --git a/src/main/java/com/autotune/analyzer/services/MetadataProfileService.java b/src/main/java/com/autotune/analyzer/services/MetadataProfileService.java new file mode 100644 index 000000000..b36463358 --- /dev/null +++ b/src/main/java/com/autotune/analyzer/services/MetadataProfileService.java @@ -0,0 +1,335 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, IBM Corporation and others. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package com.autotune.analyzer.services; + +import com.autotune.analyzer.adapters.DeviceDetailsAdapter; +import com.autotune.analyzer.adapters.RecommendationItemAdapter; +import com.autotune.analyzer.exceptions.InvalidValueException; +import com.autotune.analyzer.exceptions.MetadataProfileResponse; +import com.autotune.analyzer.metadataProfiles.MetadataProfile; +import com.autotune.analyzer.metadataProfiles.MetadataProfileCollection; +import com.autotune.analyzer.metadataProfiles.utils.MetadataProfileUtil; +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.common.data.result.ContainerData; +import com.autotune.common.data.system.info.device.DeviceDetails; +import com.autotune.database.dao.ExperimentDAOImpl; +import com.autotune.database.service.ExperimentDBService; +import com.autotune.utils.KruizeConstants; +import com.autotune.utils.KruizeSupportedTypes; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.gson.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Serial; +import java.lang.reflect.Type; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import static com.autotune.analyzer.utils.AnalyzerConstants.ServiceConstants.CHARACTER_ENCODING; +import static com.autotune.analyzer.utils.AnalyzerConstants.ServiceConstants.JSON_CONTENT_TYPE; + +@WebServlet(asyncSupported = true) +public class MetadataProfileService extends HttpServlet{ + @Serial + private static final long serialVersionUID = 1L; + private static final Logger LOGGER = LoggerFactory.getLogger(MetadataProfileService.class); + private ConcurrentHashMap metadataProfilesMap; + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + metadataProfilesMap = (ConcurrentHashMap) getServletContext() + .getAttribute(AnalyzerConstants.MetadataProfileConstants.METADATA_PROFILE_MAP); + } + + /** + * Validate and create new metadata Profile. + * + * @param request + * @param response + * @throws IOException + */ + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { + try { + Map metadataProfilesMap = new ConcurrentHashMap<>(); + String inputData = request.getReader().lines().collect(Collectors.joining()); + MetadataProfile metadataProfile = Converters.KruizeObjectConverters.convertInputJSONToCreateMetadataProfile(inputData); + ValidationOutputData validationOutputData = MetadataProfileUtil.validateAndAddMetadataProfile(metadataProfilesMap, metadataProfile); + if (validationOutputData.isSuccess()) { + ValidationOutputData addedToDB = new ExperimentDBService().addMetadataProfileToDB(metadataProfile); + if (addedToDB.isSuccess()) { + metadataProfilesMap.put(String.valueOf(metadataProfile.getMetadata().get("name")), metadataProfile); + getServletContext().setAttribute(AnalyzerConstants.MetadataProfileConstants.METADATA_PROFILE_MAP, metadataProfilesMap); + LOGGER.debug(KruizeConstants.MetadataProfileAPIMessages.ADD_METADATA_PROFILE_TO_DB_WITH_VERSION, + metadataProfile.getMetadata().get("name").asText(), metadataProfile.getProfile_version()); + // Store metadata profile in-memory collection + MetadataProfileCollection metadataProfileCollection = MetadataProfileCollection.getInstance(); + metadataProfileCollection.addMetadataProfile(metadataProfile); + + sendSuccessResponse(response, String.format(KruizeConstants.MetadataProfileAPIMessages.CREATE_METADATA_PROFILE_SUCCESS_MSG, metadataProfile.getMetadata().get("name").asText())); + } else { + sendErrorResponse(response, null, HttpServletResponse.SC_BAD_REQUEST, addedToDB.getMessage()); + } + } else + sendErrorResponse(response, null, validationOutputData.getErrorCode(), validationOutputData.getMessage()); + } catch (Exception e) { + sendErrorResponse(response, e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Validation failed: " + e.getMessage()); + } catch (InvalidValueException e) { + throw new RuntimeException(e); + } + } + + /** + * Get List of Metadata Profiles + * + * @param request + * @param response + * @throws ServletException + * @throws IOException + */ + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType(JSON_CONTENT_TYPE); + response.setCharacterEncoding(CHARACTER_ENCODING); + response.setStatus(HttpServletResponse.SC_OK); + String gsonStr = "[]"; + + ConcurrentHashMap metadataProfilesMap = new ConcurrentHashMap<>(); + String metadataProfileName = request.getParameter(AnalyzerConstants.MetadataProfileConstants.METADATA_PROFILE_NAME); + String verbose = request.getParameter(AnalyzerConstants.ServiceConstants.VERBOSE); + String internalVerbose = "false"; + boolean error = false; + + // validate Query params + Set invalidParams = new HashSet<>(); + for (String param : request.getParameterMap().keySet()) { + if (!KruizeSupportedTypes.LIST_METADATA_PROFILES_QUERY_PARAMS_SUPPORTED.contains(param)) { + invalidParams.add(param); + } + } + + // Fetch metadata profiles based on the query parameters using the in-memory storage collection + try { + if (invalidParams.isEmpty()) { + if (null != verbose) { + internalVerbose = verbose; + } + + try { + if (null != metadataProfileName && !metadataProfileName.isEmpty()) { + internalVerbose = "true"; + loadMetadataProfilesFromCollection(metadataProfilesMap, metadataProfileName); + } else { + loadAllMetadataProfilesFromCollection(metadataProfilesMap); + } + + // Check if metadata profile exists + if (metadataProfileName != null && !metadataProfilesMap.containsKey(metadataProfileName)) { + error = true; + sendErrorResponse( + response, + new Exception(AnalyzerErrorConstants.APIErrors.ListMetadataProfileAPI.INVALID_METADATA_PROFILE_NAME_EXCPTN), + HttpServletResponse.SC_BAD_REQUEST, + String.format(AnalyzerErrorConstants.APIErrors.ListMetadataProfileAPI.INVALID_METADATA_PROFILE_NAME_MSG, metadataProfileName) + ); + } else if (null == metadataProfileName && metadataProfilesMap.isEmpty()) { + error = true; + sendErrorResponse( + response, + new Exception(AnalyzerErrorConstants.APIErrors.ListMetadataProfileAPI.NO_METADATA_PROFILES_EXCPTN), + HttpServletResponse.SC_BAD_REQUEST, + AnalyzerErrorConstants.APIErrors.ListMetadataProfileAPI.NO_METADATA_PROFILES + ); + } + + if (!error) { + Collection values = metadataProfilesMap.values(); + // create Gson Object + Gson gsonObj = createGsonObject(); + + if (internalVerbose.equals("false")) { + Collection filteredValues = new ArrayList<>(); + for(MetadataProfile metadataProfile : values) { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("name", metadataProfile.getMetadata().get("name").asText()); + filteredValues.add(jsonObject); + } + gsonStr = gsonObj.toJson(filteredValues); + } else { + gsonStr = gsonObj.toJson(values); + } + response.getWriter().println(gsonStr); + response.getWriter().close(); + } + } catch (Exception e) { + LOGGER.error("Exception: {}", e.getMessage()); + e.printStackTrace(); + sendErrorResponse(response, e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); + } + } else { + sendErrorResponse( + response, + new Exception(AnalyzerErrorConstants.APIErrors.ListMetadataProfileAPI.INVALID_QUERY_PARAM), + HttpServletResponse.SC_BAD_REQUEST, + String.format(AnalyzerErrorConstants.APIErrors.ListMetadataProfileAPI.INVALID_QUERY_PARAM, invalidParams) + ); + } + } catch (Exception e) { + LOGGER.error(KruizeConstants.MetadataProfileAPIMessages.LOAD_METADATA_PROFILE_FAILURE, e.getMessage()); + sendErrorResponse(response, e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); + } + } + + /** + * TODO: Need to implement + * Update Metadata Profile + * + * @param req + * @param resp + * @throws ServletException + * @throws IOException + */ + @Override + protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + super.doPut(req, resp); + } + + + /** + * Send success response in case of no errors or exceptions. + * + * @param response + * @param message + * @throws IOException + */ + private void sendSuccessResponse(HttpServletResponse response, String message) throws IOException { + response.setContentType(JSON_CONTENT_TYPE); + response.setCharacterEncoding(CHARACTER_ENCODING); + response.setStatus(HttpServletResponse.SC_CREATED); + PrintWriter out = response.getWriter(); + out.append( + new Gson().toJson( + new MetadataProfileResponse(message + + KruizeConstants.MetadataProfileAPIMessages.VIEW_METADATA_PROFILES_MSG, + HttpServletResponse.SC_CREATED, "", "SUCCESS") + ) + ); + out.flush(); + } + + /** + * Send response containing corresponding error message in case of failures and exceptions + * + * @param response + * @param e + * @param httpStatusCode + * @param errorMsg + * @throws IOException + */ + public void sendErrorResponse(HttpServletResponse response, Exception e, int httpStatusCode, String errorMsg) throws + IOException { + if (null != e) { + LOGGER.error(e.toString()); + if (null == errorMsg) + errorMsg = e.getMessage(); + } + response.sendError(httpStatusCode, errorMsg); + } + + private void loadMetadataProfilesFromCollection(Map metadataProfilesMap, String metadataProfileName) { + try { + MetadataProfileCollection metadataProfileCollection = MetadataProfileCollection.getInstance(); + if (null != metadataProfileName && !metadataProfileName.isEmpty()) { + MetadataProfile metadataProfile = metadataProfileCollection.getMetadataProfileCollection().get(metadataProfileName); + metadataProfilesMap.put(metadataProfileName, metadataProfile); + } + } catch (Exception e) { + LOGGER.error("Failed to load saved metadata profile data: {} ", e.getMessage()); + } + } + + private void loadAllMetadataProfilesFromCollection(Map metadataProfilesMap) { + try { + MetadataProfileCollection metadataProfileCollection = MetadataProfileCollection.getInstance(); + metadataProfilesMap.putAll(metadataProfileCollection.getMetadataProfileCollection()); + } catch (Exception e) { + LOGGER.error("Failed to load all the metadata profiles data: {} ", e.getMessage()); + } + } + + private Gson createGsonObject() { + return new GsonBuilder() + .disableHtmlEscaping() + .setPrettyPrinting() + .enableComplexMapKeySerialization() + .registerTypeAdapter(Date.class, new GsonUTCDateAdapter()) + .registerTypeAdapter(AnalyzerConstants.RecommendationItem.class, new RecommendationItemAdapter()) + .registerTypeAdapter(DeviceDetails.class, new DeviceDetailsAdapter()) + // a custom serializer for serializing metadata of JsonNode type. + .registerTypeAdapter(JsonNode.class, new JsonSerializer() { + @Override + public JsonElement serialize(JsonNode jsonNode, Type typeOfSrc, JsonSerializationContext context) { + if (jsonNode instanceof ObjectNode) { + ObjectNode objectNode = (ObjectNode) jsonNode; + JsonObject metadataJson = new JsonObject(); + + // Extract the "name" field directly if it exists + if (objectNode.has("name")) { + metadataJson.addProperty("name", objectNode.get("name").asText()); + } + + return metadataJson; + } + return context.serialize(jsonNode); + } + }) + .setExclusionStrategies(new ExclusionStrategy() { + @Override + public boolean shouldSkipField(FieldAttributes f) { + return f.getDeclaringClass() == Metric.class && ( + f.getName().equals("trialSummaryResult") + || f.getName().equals("cycleDataMap") + ) || + f.getDeclaringClass() == ContainerData.class && ( + f.getName().equalsIgnoreCase("metrics") + ); + } + + @Override + public boolean shouldSkipClass(Class aClass) { + return false; + } + }) + .create(); + } +} diff --git a/src/main/java/com/autotune/analyzer/utils/AnalyzerErrorConstants.java b/src/main/java/com/autotune/analyzer/utils/AnalyzerErrorConstants.java index b5a092594..8bb64f0c3 100644 --- a/src/main/java/com/autotune/analyzer/utils/AnalyzerErrorConstants.java +++ b/src/main/java/com/autotune/analyzer/utils/AnalyzerErrorConstants.java @@ -267,6 +267,30 @@ public DeleteMetricProfileAPI() { public static final String DELETE_METRIC_PROFILE_ENTRY_NOT_FOUND_WITH_NAME = "KruizeMetricProfileEntry not found with metric profile name: "; public static final String DELETE_METRIC_PROFILE_ENTRY_ERROR_MSG = "Not able to delete metric profile for metric profile {} due to {}"; } + + public static final class ListMetadataProfileAPI { + public ListMetadataProfileAPI() { + } + public static final String INVALID_QUERY_PARAM = "The query param(s) - %s is/are invalid"; + public static final String INVALID_QUERY_PARAM_VALUE = "The query param value(s) is/are invalid"; + public static final String INVALID_METADATA_PROFILE_NAME_EXCPTN = "Invalid Metadata Profile Name"; + public static final String INVALID_METADATA_PROFILE_NAME_MSG = "Given metadata profile name - %s either does not exist or is not valid"; + public static final String NO_METADATA_PROFILES_EXCPTN = "No metadata profile"; + public static final String NO_METADATA_PROFILES = "No metadata profiles found!"; + } + + public static final class DeleteMetadataProfileAPI { + public DeleteMetadataProfileAPI() { + } + public static final String INVALID_METADATA_PROFILE_NAME_EXCPTN = "Invalid Metadata Profile Name"; + public static final String INVALID_METADATA_PROFILE_NAME_MSG = "Given metadata profile name - %s either does not exist or is not valid"; + public static final String MISSING_METADATA_PROFILE_NAME_EXCPTN = "Missing Metadata Profile Name"; + public static final String MISSING_METADATA_PROFILE_NAME_MSG = "Missing metadata profile 'name' parameter"; + public static final String DELETE_METADATA_PROFILE_FROM_DB_FAILURE_MSG = "Failed to delete metadata profile from DB: %s"; + public static final String DELETE_METADATA_PROFILE_FAILURE_MSG = "Failed to delete the specified metadata profile data: %s"; + public static final String DELETE_METADATA_PROFILE_ENTRY_NOT_FOUND_WITH_NAME = "KruizeMetadataProfileEntry not found with metadata profile name: "; + public static final String DELETE_METADATA_PROFILE_ENTRY_ERROR_MSG = "Not able to delete metadata profile for metadata profile {} due to {}"; + } } public static final class ConversionErrors { diff --git a/src/main/java/com/autotune/database/helper/DBHelpers.java b/src/main/java/com/autotune/database/helper/DBHelpers.java index cc92b9d27..65f70e3a1 100644 --- a/src/main/java/com/autotune/database/helper/DBHelpers.java +++ b/src/main/java/com/autotune/database/helper/DBHelpers.java @@ -31,6 +31,7 @@ import com.autotune.analyzer.utils.GsonUTCDateAdapter; import com.autotune.common.auth.AuthenticationConfig; import com.autotune.common.data.dataSourceMetadata.*; +import com.autotune.common.data.metrics.Metric; import com.autotune.common.data.result.ContainerData; import com.autotune.common.data.result.ExperimentResultData; import com.autotune.common.data.result.NamespaceData; @@ -1421,6 +1422,85 @@ public static KruizeAuthenticationEntry convertAuthDetailsToAuthDetailsDBObj(Aut } return kruizeAuthenticationEntry; } + + /** + * Converts List of KruizeLMMetadataProfileEntry DB objects to List of MetadataProfile objects + * + * @param kruizeMetadataProfileEntryList List of KruizeLMMetadataProfileEntry DB objects + * @return List of MetadataProfile objects + */ + public static List convertMetadataProfileEntryToMetadataProfileObject(List kruizeMetadataProfileEntryList) throws Exception { + List metadataProfiles = new ArrayList<>(); + int failureThreshHold = kruizeMetadataProfileEntryList.size(); + int failureCount = 0; + for (KruizeLMMetadataProfileEntry entry : kruizeMetadataProfileEntryList) { + try { + JsonNode metadata = entry.getMetadata(); + JsonNode query_variables = entry.getQuery_variables(); + ArrayList queryVariablesList = new ArrayList<>(); + + if (query_variables.isArray()) { + for (JsonNode node : query_variables) { + String metric_rawJson = node.toString(); + Metric metric = new Gson().fromJson(metric_rawJson, Metric.class); + queryVariablesList.add(metric); + } + } + + MetadataProfile metadataProfile = new MetadataProfile( + entry.getApi_version(), entry.getKind(), metadata, entry.getProfile_version(), entry.getK8s_type(), queryVariablesList); + metadataProfiles.add(metadataProfile); + } catch (Exception e) { + LOGGER.error("Error occurred while reading from MetadataProfile DB object due to : {}", e.getMessage()); + LOGGER.error(entry.toString()); + failureCount++; + } + } + if (failureThreshHold > 0 && failureCount == failureThreshHold) { + throw new Exception("None of the Metadata Profiles loaded from DB."); + } + + return metadataProfiles; + } + + /** + * Converts MetadataProfile object to KruizeLMMetadataProfileEntry object + * + * @param metadataProfile MetadataProfile object + * @return KruizeLMMetadataProfileEntry objects + */ + public static KruizeLMMetadataProfileEntry convertMetadataProfileObjToMetadataProfileDBObj(MetadataProfile metadataProfile) { + KruizeLMMetadataProfileEntry kruizeMetadataProfileEntry = null; + try { + kruizeMetadataProfileEntry = new KruizeMetadataProfileEntry(); + kruizeMetadataProfileEntry.setApi_version(metadataProfile.getApiVersion()); + kruizeMetadataProfileEntry.setKind(metadataProfile.getKind()); + kruizeMetadataProfileEntry.setProfile_version(metadataProfile.getProfile_version()); + kruizeMetadataProfileEntry.setK8s_type(metadataProfile.getK8s_type()); + + ObjectMapper objectMapper = new ObjectMapper(); + + try { + JsonNode metadataNode = objectMapper.readTree(metadataProfile.getMetadata().toString()); + kruizeMetadataProfileEntry.setMetadata(metadataNode); + } catch (JsonProcessingException e) { + throw new Exception("Error while creating metadataProfile due to : " + e.getMessage()); + } + kruizeMetadataProfileEntry.setName(metadataProfile.getMetadata().get("name").asText()); + + try { + kruizeMetadataProfileEntry.setQuery_variables( + objectMapper.readTree(new Gson().toJson(metadataProfile.getQueryVariables()))); + } catch (JsonProcessingException e) { + throw new Exception("Error while creating query_variables data due to : " + e.getMessage()); + } + } catch (Exception e) { + LOGGER.error("Error occurred while converting MetadataProfile Object to MetadataProfile table due to {}", e.getMessage()); + e.printStackTrace(); + } + return kruizeMetadataProfileEntry; + } + } } diff --git a/src/main/java/com/autotune/database/service/ExperimentDBService.java b/src/main/java/com/autotune/database/service/ExperimentDBService.java index 1572c4873..a6e24fe1d 100644 --- a/src/main/java/com/autotune/database/service/ExperimentDBService.java +++ b/src/main/java/com/autotune/database/service/ExperimentDBService.java @@ -198,6 +198,23 @@ public void loadAllMetricProfiles(Map metricProfileM } } + /** + * Loads All Metadata Profiles from database + * + * @param metadataProfileMap Metadata profile map to store the objects to be added + * @return ValidationOutputData object + */ + public void loadAllMetadataProfiles(Map metadataProfileMap) throws Exception { + List entries = experimentDAO.loadAllMetadataProfiles(); + if (null != entries && !entries.isEmpty()) { + List metadataProfiles = DBHelpers.Converters.KruizeObjectConverters.convertMetadataProfileEntryToMetadataProfileObject(entries); + if (!metadataProfiles.isEmpty()) { + metadataProfiles.forEach(metadataProfile -> + MetadataProfileUtil.addMetadataProfile(metadataProfileMap, metadataProfile)); + } + } + } + public boolean loadResultsFromDBByName(Map mainKruizeExperimentMap, String experimentName, Timestamp calculated_start_time, Timestamp interval_end_time) throws Exception { ExperimentInterface experimentInterface = new ExperimentInterfaceImpl(); KruizeObject kruizeObject = mainKruizeExperimentMap.get(experimentName); @@ -389,6 +406,23 @@ public ValidationOutputData addMetricProfileToDB(PerformanceProfile metricProfil return validationOutputData; } + /** + * Adds Metadata Profile to kruizeLMMetadataProfileEntry + * + * @param metadataProfile Metadata profile object to be added + * @return ValidationOutputData object + */ + public ValidationOutputData addMetadataProfileToDB(MetadataProfile metadataProfile) { + ValidationOutputData validationOutputData = new ValidationOutputData(false, null, null); + try { + KruizeLMMetadataProfileEntry kruizeMetadataProfileEntry = DBHelpers.Converters.KruizeObjectConverters.convertMetadataProfileObjToMetadataProfileDBObj(metadataProfile); + validationOutputData = this.experimentDAO.addMetadataProfileToDB(kruizeMetadataProfileEntry); + } catch (Exception e) { + LOGGER.error("Not able to save Metadata Profile due to {}", e.getMessage()); + } + 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, diff --git a/src/main/java/com/autotune/utils/KruizeConstants.java b/src/main/java/com/autotune/utils/KruizeConstants.java index ff0b52592..86693bc01 100644 --- a/src/main/java/com/autotune/utils/KruizeConstants.java +++ b/src/main/java/com/autotune/utils/KruizeConstants.java @@ -89,6 +89,18 @@ public static class MetricProfileConstants { public static final String METRIC_PROFILE_ADDED = "MetricProfile added to the collection successfully: "; } + public static final class MetadataProfileAPIMessages { + public static final String CREATE_METADATA_PROFILE_SUCCESS_MSG = "Metadata Profile : %s created successfully."; + public static final String VIEW_METADATA_PROFILES_MSG = " View Metadata Profiles at /listMetadataProfiles"; + public static final String LOAD_METADATA_PROFILE_FAILURE = "Failed to load saved metadata profile data: {}"; + public static final String ADD_METADATA_PROFILE_TO_DB_WITH_VERSION = "Added Metadata Profile : {} into the DB with version: {}"; + public static final String DELETE_METADATA_PROFILE_SUCCESS_MSG = "Metadata profile: %s deleted successfully."; + public static final String DELETE_METADATA_PROFILE_FROM_DB_SUCCESS_MSG = "Metadata profile deleted successfully from the DB."; + + private MetadataProfileAPIMessages() { + } + } + /** * Holds the constants of env vars and values to start Autotune in different Modes */ diff --git a/src/main/java/com/autotune/utils/KruizeSupportedTypes.java b/src/main/java/com/autotune/utils/KruizeSupportedTypes.java index 638fd2e5b..b5ab3d697 100644 --- a/src/main/java/com/autotune/utils/KruizeSupportedTypes.java +++ b/src/main/java/com/autotune/utils/KruizeSupportedTypes.java @@ -69,6 +69,9 @@ public class KruizeSupportedTypes { public static final Set LIST_METRIC_PROFILES_QUERY_PARAMS_SUPPORTED = new HashSet<>(Arrays.asList( "name", "verbose" )); + public static final Set LIST_METADATA_PROFILES_QUERY_PARAMS_SUPPORTED = new HashSet<>(Arrays.asList( + "name", "verbose" + )); private KruizeSupportedTypes() { } diff --git a/src/main/java/com/autotune/utils/ServerContext.java b/src/main/java/com/autotune/utils/ServerContext.java index eac7f6079..67398b45e 100644 --- a/src/main/java/com/autotune/utils/ServerContext.java +++ b/src/main/java/com/autotune/utils/ServerContext.java @@ -46,6 +46,8 @@ public class ServerContext { public static final String CREATE_METRIC_PROFILE = ROOT_CONTEXT + "createMetricProfile"; public static final String LIST_METRIC_PROFILES = ROOT_CONTEXT + "listMetricProfiles"; public static final String DELETE_METRIC_PROFILE = ROOT_CONTEXT + "deleteMetricProfile"; + public static final String CREATE_METADATA_PROFILE = ROOT_CONTEXT + "createMetadataProfile"; + public static final String LIST_METADATA_PROFILES = ROOT_CONTEXT + "listMetadataProfiles"; public static final String KRUIZE_SERVER_URL = "http://localhost:" + KRUIZE_SERVER_PORT; public static final String SEARCH_SPACE_END_POINT = KRUIZE_SERVER_URL + SEARCH_SPACE;