diff --git a/migrations/kruize_local_ddl.sql b/migrations/kruize_local_ddl.sql index 48781f500..0c25a38a5 100644 --- a/migrations/kruize_local_ddl.sql +++ b/migrations/kruize_local_ddl.sql @@ -1,4 +1,5 @@ -create table IF NOT EXISTS kruize_datasources (version varchar(255), name varchar(255), provider varchar(255), serviceName varchar(255), namespace varchar(255), url varchar(255), authentication jsonb, primary key (name)); +create table IF NOT EXISTS kruize_authentication (id serial, authentication_type varchar(255), credentials jsonb, service_type varchar(255), primary key (id)); +create table IF NOT EXISTS kruize_datasources (version varchar(255), name varchar(255), provider varchar(255), serviceName varchar(255), namespace varchar(255), url varchar(255), authentication_id serial, FOREIGN KEY (authentication_id) REFERENCES kruize_authentication(id), primary key (name)); create table IF NOT EXISTS kruize_dsmetadata (id serial, version varchar(255), datasource_name varchar(255), cluster_name varchar(255), namespace varchar(255), workload_type varchar(255), workload_name varchar(255), container_name varchar(255), container_image_name varchar(255), primary key (id)); alter table kruize_experiments add column experiment_type varchar(255), add column metadata_id bigint references kruize_dsmetadata(id), alter column datasource type varchar(255); create table IF NOT EXISTS kruize_metric_profiles (api_version varchar(255), kind varchar(255), metadata jsonb, name varchar(255) not null, k8s_type varchar(255), profile_version float(53) not null, slo jsonb, primary key (name)); diff --git a/src/main/java/com/autotune/common/auth/AuthType.java b/src/main/java/com/autotune/common/auth/AuthType.java new file mode 100644 index 000000000..84049e102 --- /dev/null +++ b/src/main/java/com/autotune/common/auth/AuthType.java @@ -0,0 +1,5 @@ +package com.autotune.common.auth; + +public enum AuthType { + BASIC, BEARER, API_KEY, OAUTH2, NONE +} diff --git a/src/main/java/com/autotune/common/auth/AuthenticationConfig.java b/src/main/java/com/autotune/common/auth/AuthenticationConfig.java index 6eebdf2a5..afa815778 100644 --- a/src/main/java/com/autotune/common/auth/AuthenticationConfig.java +++ b/src/main/java/com/autotune/common/auth/AuthenticationConfig.java @@ -1,17 +1,18 @@ package com.autotune.common.auth; -import com.autotune.analyzer.utils.AnalyzerConstants; import com.autotune.utils.KruizeConstants; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Objects; + public class AuthenticationConfig { - private String type; // "basic", "bearer", "apiKey", "oauth2" + private AuthType type; private Credentials credentials; private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticationConfig.class); - public AuthenticationConfig(String type, Credentials credentials) { + public AuthenticationConfig(AuthType type, Credentials credentials) { this.type = type; this.credentials = credentials; } @@ -19,7 +20,7 @@ public AuthenticationConfig(String type, Credentials credentials) { public AuthenticationConfig() { } - public String getType() { + public AuthType getType() { return type; } @@ -30,27 +31,35 @@ public Credentials getCredentials() { public static AuthenticationConfig createAuthenticationConfigObject(JSONObject authenticationObj) { // Parse and map authentication methods if they exist if (authenticationObj != null) { - String type = authenticationObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_TYPE); + AuthType type = AuthType.valueOf(authenticationObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_TYPE).toUpperCase()); JSONObject credentialsObj = authenticationObj.getJSONObject(KruizeConstants.AuthenticationConstants.AUTHENTICATION_CREDENTIALS); - Credentials credentials = new Credentials(); - switch (type.toLowerCase()) { - case KruizeConstants.AuthenticationConstants.BASIC: - credentials.setUsername(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_USERNAME)); - credentials.setPassword(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_PASSWORD)); + Credentials credentials = null; // Initialize credentials as null, and create specific subclass instances based on the type + switch (type) { + case BASIC: + BasicAuthCredentials basicCredentials = new BasicAuthCredentials(); + basicCredentials.setUsername(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_USERNAME)); + basicCredentials.setPassword(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_PASSWORD)); + credentials = basicCredentials; break; - case KruizeConstants.AuthenticationConstants.BEARER: - credentials.setTokenFilePath(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_TOKEN_FILE)); + case BEARER: + BearerTokenCredentials bearerCredentials = new BearerTokenCredentials(); + bearerCredentials.setTokenFilePath(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_TOKEN_FILE)); + credentials = bearerCredentials; break; - case KruizeConstants.AuthenticationConstants.API_KEY: - credentials.setApiKey(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_API_KEY)); - credentials.setHeaderName(credentialsObj.optString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_HEADER_NAME, "X-API-Key")); + case API_KEY: + ApiKeyCredentials apiKeyCredentials = new ApiKeyCredentials(); + apiKeyCredentials.setApiKey(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_API_KEY)); + apiKeyCredentials.setHeaderName(credentialsObj.optString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_HEADER_NAME, "X-API-Key")); + credentials = apiKeyCredentials; break; - case KruizeConstants.AuthenticationConstants.OAUTH2: - credentials.setTokenEndpoint(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_TOKEN_ENDPOINT)); - credentials.setClientId(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_CLIENT_ID)); - credentials.setClientSecret(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_CLIENT_SECRET)); - credentials.setGrantType(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_GRANT_TYPE)); + case OAUTH2: + OAuth2Credentials oauth2Credentials = new OAuth2Credentials(); + oauth2Credentials.setTokenEndpoint(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_TOKEN_ENDPOINT)); + oauth2Credentials.setClientId(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_CLIENT_ID)); + oauth2Credentials.setClientSecret(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_CLIENT_SECRET)); + oauth2Credentials.setGrantType(credentialsObj.getString(KruizeConstants.AuthenticationConstants.AUTHENTICATION_GRANT_TYPE)); + credentials = oauth2Credentials; break; default: LOGGER.error(KruizeConstants.AuthenticationConstants.UNKNOWN_AUTHENTICATION + "{}", type); @@ -63,14 +72,20 @@ public static AuthenticationConfig createAuthenticationConfigObject(JSONObject a // Static method to return a no-auth config public static AuthenticationConfig noAuth() { - return new AuthenticationConfig(AnalyzerConstants.NONE, null); + return new AuthenticationConfig(AuthType.NONE, null); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AuthenticationConfig that = (AuthenticationConfig) o; + return Objects.equals(type, that.type) && + Objects.equals(credentials, that.credentials); } @Override - public String toString() { - return "AuthenticationConfig{" + - "type='" + type + '\'' + - ", credentials=" + credentials + - '}'; + public int hashCode() { + return Objects.hash(type, credentials); } } diff --git a/src/main/java/com/autotune/common/auth/AuthenticationStrategyFactory.java b/src/main/java/com/autotune/common/auth/AuthenticationStrategyFactory.java index c81a6075d..72c9e5a20 100644 --- a/src/main/java/com/autotune/common/auth/AuthenticationStrategyFactory.java +++ b/src/main/java/com/autotune/common/auth/AuthenticationStrategyFactory.java @@ -5,24 +5,24 @@ public class AuthenticationStrategyFactory { public static AuthenticationStrategy createAuthenticationStrategy(AuthenticationConfig authConfig) { - String type = authConfig.getType(); + AuthType type = authConfig.getType(); switch (type) { - case KruizeConstants.AuthenticationConstants.NONE: + case NONE: return new NoAuthStrategy(); - case KruizeConstants.AuthenticationConstants.BASIC: - String username = authConfig.getCredentials().getUsername(); - String password = authConfig.getCredentials().getPassword(); + case BASIC: + String username = ((BasicAuthCredentials) authConfig.getCredentials()).getUsername(); + String password = ((BasicAuthCredentials) authConfig.getCredentials()).getPassword(); return new BasicAuthenticationStrategy(username, password); - case KruizeConstants.AuthenticationConstants.BEARER: - String tokenFilePath = authConfig.getCredentials().getTokenFilePath(); + case BEARER: + String tokenFilePath = ((BearerTokenCredentials) authConfig.getCredentials()).getTokenFilePath(); return new BearerAuthenticationStrategy(tokenFilePath); - case KruizeConstants.AuthenticationConstants.API_KEY: - String apiKey = authConfig.getCredentials().getApiKey(); + case API_KEY: + String apiKey = ((ApiKeyCredentials) authConfig.getCredentials()).getApiKey(); return new APIKeyAuthenticationStrategy(apiKey); - case KruizeConstants.AuthenticationConstants.OAUTH2: - String tokenEndpoint = authConfig.getCredentials().getTokenEndpoint(); - String clientId = authConfig.getCredentials().getClientId(); - String clientSecret = authConfig.getCredentials().getClientSecret(); + case OAUTH2: + String tokenEndpoint = ((OAuth2Credentials) authConfig.getCredentials()).getTokenEndpoint(); + String clientId = ((OAuth2Credentials) authConfig.getCredentials()).getClientId(); + String clientSecret = ((OAuth2Credentials) authConfig.getCredentials()).getClientSecret(); return new OAuth2AuthenticationStrategy(tokenEndpoint, clientId, clientSecret); default: throw new IllegalArgumentException(KruizeConstants.AuthenticationConstants.UNKNOWN_AUTHENTICATION+ type); diff --git a/src/main/java/com/autotune/common/auth/Credentials.java b/src/main/java/com/autotune/common/auth/Credentials.java index 4638eb088..541c6828c 100644 --- a/src/main/java/com/autotune/common/auth/Credentials.java +++ b/src/main/java/com/autotune/common/auth/Credentials.java @@ -1,108 +1,148 @@ package com.autotune.common.auth; -public class Credentials { - private String grantType; // OAuth2 - private String clientId; // OAuth2 - private String clientSecret; // OAuth2 - private String username; // Basic auth - private String password; // Basic auth - private String tokenEndpoint; // OAuth2 - private String tokenFilePath; // Bearer token - private String apiKey; // API key - private String headerName; // API key header name - - public Credentials(String username, String password) { - this.username = username; - this.password = password; - } +import java.util.Objects; - public Credentials() { - } +public abstract class Credentials { +} - public String getUsername() { - return username; - } +class OAuth2Credentials extends Credentials { + private String grantType; + private String clientId; + private String clientSecret; + private String tokenEndpoint; public String getGrantType() { return grantType; } - public String getClientSecret() { - return clientSecret; + public void setGrantType(String grantType) { + this.grantType = grantType; } public String getClientId() { return clientId; } - public String getTokenEndpoint() { - return tokenEndpoint; + public void setClientId(String clientId) { + this.clientId = clientId; } - public String getHeaderName() { - return headerName; + public String getClientSecret() { + return clientSecret; } - public String getApiKey() { - return apiKey; + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; } - public String getTokenFilePath() { - return tokenFilePath; + public String getTokenEndpoint() { + return tokenEndpoint; } - public String getPassword() { - return password; + public void setTokenEndpoint(String tokenEndpoint) { + this.tokenEndpoint = tokenEndpoint; } - public void setGrantType(String grantType) { - this.grantType = grantType; + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + OAuth2Credentials that = (OAuth2Credentials) o; + return Objects.equals(grantType, that.grantType) && + Objects.equals(clientId, that.clientId) && + Objects.equals(clientSecret, that.clientSecret) && + Objects.equals(tokenEndpoint, that.tokenEndpoint); } - public void setClientId(String clientId) { - this.clientId = clientId; + @Override + public int hashCode() { + return Objects.hash(grantType, clientId, clientSecret, tokenEndpoint); } +} - public void setClientSecret(String clientSecret) { - this.clientSecret = clientSecret; +class BasicAuthCredentials extends Credentials { + private String username; + private String password; + + public String getUsername() { + return username; } public void setUsername(String username) { this.username = username; } + public String getPassword() { + return password; + } + public void setPassword(String password) { this.password = password; } +} - public void setTokenEndpoint(String tokenEndpoint) { - this.tokenEndpoint = tokenEndpoint; +class BearerTokenCredentials extends Credentials { + private String tokenFilePath; + + public String getTokenFilePath() { + return tokenFilePath; } public void setTokenFilePath(String tokenFilePath) { this.tokenFilePath = tokenFilePath; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BearerTokenCredentials that = (BearerTokenCredentials) o; + return Objects.equals(tokenFilePath, that.tokenFilePath); + } + + @Override + public int hashCode() { + return Objects.hash(tokenFilePath); + } + @Override + public String toString() { + return "BearerTokenCredentials{" + + "tokenFilePath='" + tokenFilePath + '\'' + + '}'; + } +} + +class ApiKeyCredentials extends Credentials { + private String apiKey; + private String headerName; + + public String getApiKey() { + return apiKey; + } + public void setApiKey(String apiKey) { this.apiKey = apiKey; } + public String getHeaderName() { + return headerName; + } + public void setHeaderName(String headerName) { this.headerName = headerName; } @Override - public String toString() { - return "Credentials{" + - "grantType='" + grantType + '\'' + - ", clientId='" + clientId + '\'' + - ", clientSecret='" + clientSecret + '\'' + - ", username='" + username + '\'' + - ", password='" + password + '\'' + - ", tokenEndpoint='" + tokenEndpoint + '\'' + - ", tokenFilePath='" + tokenFilePath + '\'' + - ", apiKey='" + apiKey + '\'' + - ", headerName='" + headerName + '\'' + - '}'; + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ApiKeyCredentials that = (ApiKeyCredentials) o; + return Objects.equals(apiKey, that.apiKey); + } + + @Override + public int hashCode() { + return Objects.hash(apiKey); } } + diff --git a/src/main/java/com/autotune/common/data/ValidationOutputData.java b/src/main/java/com/autotune/common/data/ValidationOutputData.java index 1b26738e8..d80213ff0 100644 --- a/src/main/java/com/autotune/common/data/ValidationOutputData.java +++ b/src/main/java/com/autotune/common/data/ValidationOutputData.java @@ -22,6 +22,7 @@ public class ValidationOutputData { private boolean success; private String message; private Integer errorCode; + private Long authEntryId; public ValidationOutputData(boolean success, String message, Integer errorCode) { this.success = success; @@ -53,6 +54,14 @@ public void setErrorCode(Integer errorCode) { this.errorCode = errorCode; } + public Long getAuthEntryId() { + return authEntryId; + } + + public void setAuthEntryId(Long authEntryId) { + this.authEntryId = authEntryId; + } + @Override public String toString() { return "ValidationOutputData{" + diff --git a/src/main/java/com/autotune/common/datasource/DataSourceCollection.java b/src/main/java/com/autotune/common/datasource/DataSourceCollection.java index 9daf555a3..1ce18040d 100644 --- a/src/main/java/com/autotune/common/datasource/DataSourceCollection.java +++ b/src/main/java/com/autotune/common/datasource/DataSourceCollection.java @@ -39,6 +39,7 @@ import static com.autotune.utils.KruizeConstants.DataSourceConstants.DataSourceErrorMsgs.*; import static com.autotune.utils.KruizeConstants.DataSourceConstants.DataSourceSuccessMsgs.DATASOURCE_ADDED; +import static com.autotune.utils.KruizeConstants.DataSourceConstants.DataSourceSuccessMsgs.DATASOURCE_AUTH_ADDED_DB; public class DataSourceCollection { private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceCollection.class); @@ -96,7 +97,7 @@ public void addDataSource(DataSourceInfo datasource) throws DataSourceAlreadyExi final String provider = datasource.getProvider(); ValidationOutputData addedToDB = null; - LOGGER.info(KruizeConstants.DataSourceConstants.DataSourceInfoMsgs.ADDING_DATASOURCE + name); + LOGGER.info(KruizeConstants.DataSourceConstants.DataSourceInfoMsgs.ADDING_DATASOURCE, name); if (dataSourceCollection.containsKey(name)) { @@ -104,16 +105,23 @@ public void addDataSource(DataSourceInfo datasource) throws DataSourceAlreadyExi } if (provider.equalsIgnoreCase(KruizeConstants.SupportedDatasources.PROMETHEUS)) { - LOGGER.info(KruizeConstants.DataSourceConstants.DataSourceInfoMsgs.VERIFYING_DATASOURCE_REACHABILITY + name); + LOGGER.info(KruizeConstants.DataSourceConstants.DataSourceInfoMsgs.VERIFYING_DATASOURCE_REACHABILITY, name); DataSourceOperatorImpl op = DataSourceOperatorImpl.getInstance().getOperator(KruizeConstants.SupportedDatasources.PROMETHEUS); if (op.isServiceable(datasource) == CommonUtils.DatasourceReachabilityStatus.REACHABLE) { LOGGER.info(KruizeConstants.DataSourceConstants.DataSourceSuccessMsgs.DATASOURCE_SERVICEABLE); - // add the data source to DB - addedToDB = new ExperimentDBService().addDataSourceToDB(datasource); + // add the authentication details to the DB + addedToDB = new ExperimentDBService().addAuthenticationDetailsToDB(datasource.getAuthenticationConfig(), KruizeConstants.JSONKeys.DATASOURCE); if (addedToDB.isSuccess()) { - LOGGER.info(DATASOURCE_ADDED); + LOGGER.info(KruizeConstants.DataSourceConstants.DataSourceSuccessMsgs.DATASOURCE_AUTH_ADDED_DB); + // add the data source to DB + addedToDB = new ExperimentDBService().addDataSourceToDB(datasource, addedToDB); + if (addedToDB.isSuccess()) { + LOGGER.info(DATASOURCE_AUTH_ADDED_DB); + } else { + LOGGER.error("{}: {}", DATASOURCE_NOT_SERVICEABLE, addedToDB.getMessage()); + } } else { - LOGGER.error("{}: {}", DATASOURCE_NOT_SERVICEABLE, addedToDB.getMessage()); + LOGGER.error(DATASOURCE_AUTH_DB_INSERTION_FAILED, addedToDB.getMessage()); } dataSourceCollection.put(name, datasource); LOGGER.info(DATASOURCE_ADDED); @@ -134,7 +142,8 @@ public void addDataSource(DataSourceInfo datasource) throws DataSourceAlreadyExi public void addDataSourcesFromConfigFile(String configFileName) throws UnsupportedDataSourceProvider, DataSourceNotServiceable, DataSourceAlreadyExist, IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { String configFile = System.getenv(configFileName); - JSONObject configObject = null; + JSONObject configObject; + ValidationOutputData addedToDB; InputStream is = new FileInputStream(configFile); String jsonTxt = new String(is.readAllBytes(), StandardCharsets.UTF_8); @@ -144,49 +153,58 @@ public void addDataSourcesFromConfigFile(String configFileName) throws Unsupport for (Object dataSourceObj : dataSourceArr) { JSONObject dataSourceObject = (JSONObject) dataSourceObj; String name = dataSourceObject.getString(KruizeConstants.DataSourceConstants.DATASOURCE_NAME); - // check the DB if the datasource already exists + // Fetch the existing datasource from the DB (if it exists) + DataSourceInfo dataSourceInfo = null; try { - DataSourceInfo dataSourceInfo = new ExperimentDBService().loadDataSourceFromDBByName(name); - if (null != dataSourceInfo) { - LOGGER.error("Datasource: {} already exists!", name); - // add the auth details to local object - AuthenticationConfig authConfig = getAuthenticationDetails(dataSourceObject, name); - dataSourceInfo.setAuthenticationConfig(authConfig); - dataSourceCollection.put(name, dataSourceInfo); - continue; - } + dataSourceInfo = new ExperimentDBService().loadDataSourceFromDBByName(name); } catch (Exception e) { LOGGER.error(DATASOURCE_DB_LOAD_FAILED, name, e.getMessage()); } - String provider = dataSourceObject.getString(KruizeConstants.DataSourceConstants.DATASOURCE_PROVIDER); - String serviceName = dataSourceObject.getString(KruizeConstants.DataSourceConstants.DATASOURCE_SERVICE_NAME); - String namespace = dataSourceObject.getString(KruizeConstants.DataSourceConstants.DATASOURCE_SERVICE_NAMESPACE); - String dataSourceURL = dataSourceObject.getString(KruizeConstants.DataSourceConstants.DATASOURCE_URL); - AuthenticationConfig authConfig = getAuthenticationDetails(dataSourceObject, name); - try { - JSONObject authenticationObj = dataSourceObject.optJSONObject(KruizeConstants.AuthenticationConstants.AUTHENTICATION); - // create the corresponding authentication object - authConfig = AuthenticationConfig.createAuthenticationConfigObject(authenticationObj); - } catch (Exception e) { - LOGGER.warn(DATASOURCE_DB_AUTH_LOAD_FAILED, name, e.getMessage()); - authConfig = AuthenticationConfig.noAuth(); - } - - DataSourceInfo datasource; - // Validate input - if (!validateInput(name, provider, serviceName, dataSourceURL, namespace)) { - continue; - } - if (dataSourceURL.isEmpty()) { - datasource = new DataSourceInfo(name, provider, serviceName, namespace, null); + if (null != dataSourceInfo) { + LOGGER.debug(KruizeConstants.DataSourceConstants.DataSourceInfoMsgs.CHECK_DATASOURCE_UPDATES, name); + // get the updated authentication details and compare it with the one in the DB + AuthenticationConfig modifiedAuthConfig = getAuthenticationDetails(dataSourceObject, name); + // Compare with the existing authentication config + if (dataSourceInfo.hasAuthChanged(modifiedAuthConfig)) { + LOGGER.info(KruizeConstants.DataSourceConstants.DataSourceInfoMsgs.DATASOURCE_AUTH_CHANGED, name); + // check the datasource with the new config + dataSourceInfo.updateAuthConfig(modifiedAuthConfig); + DataSourceOperatorImpl op = DataSourceOperatorImpl.getInstance().getOperator(KruizeConstants.SupportedDatasources.PROMETHEUS); + if (op.isServiceable(dataSourceInfo) == CommonUtils.DatasourceReachabilityStatus.REACHABLE) { + // update the authentication details in the DB + addedToDB = new ExperimentDBService().addAuthenticationDetailsToDB(dataSourceInfo.getAuthenticationConfig(), KruizeConstants.JSONKeys.DATASOURCE); + if (addedToDB.isSuccess()) { + LOGGER.debug(KruizeConstants.DataSourceConstants.DataSourceSuccessMsgs.DATASOURCE_AUTH_UPDATED_DB); + } else { + LOGGER.error(DATASOURCE_AUTH_DB_UPDATE_FAILED, addedToDB.getMessage()); + } + } else { + LOGGER.error(DATASOURCE_AUTH_UPDATE_INVALID); + } + } else { + LOGGER.debug(KruizeConstants.DataSourceConstants.DataSourceInfoMsgs.DATASOURCE_AUTH_UNCHANGED, name); + return; + } } else { - datasource = new DataSourceInfo(name, provider, serviceName, namespace, new URL(dataSourceURL)); + String provider = dataSourceObject.getString(KruizeConstants.DataSourceConstants.DATASOURCE_PROVIDER); + String serviceName = dataSourceObject.getString(KruizeConstants.DataSourceConstants.DATASOURCE_SERVICE_NAME); + String namespace = dataSourceObject.getString(KruizeConstants.DataSourceConstants.DATASOURCE_SERVICE_NAMESPACE); + String dataSourceURL = dataSourceObject.getString(KruizeConstants.DataSourceConstants.DATASOURCE_URL); + AuthenticationConfig authConfig = getAuthenticationDetails(dataSourceObject, name); + + // Validate input + if (!validateInput(name, provider, serviceName, dataSourceURL, namespace)) { //TODO: add validations for auth + continue; + } + if (dataSourceURL.isEmpty()) { + dataSourceInfo = new DataSourceInfo(name, provider, serviceName, namespace, null, authConfig); + } else { + dataSourceInfo = new DataSourceInfo(name, provider, serviceName, namespace, new URL(dataSourceURL), authConfig); + } + // add/update the datasource + addDataSource(dataSourceInfo); } - // set the authentication config - datasource.setAuthenticationConfig(authConfig); - addDataSource(datasource); } - } private AuthenticationConfig getAuthenticationDetails(JSONObject dataSourceObject, String name) { diff --git a/src/main/java/com/autotune/common/datasource/DataSourceInfo.java b/src/main/java/com/autotune/common/datasource/DataSourceInfo.java index eed8a2855..ea362aac9 100644 --- a/src/main/java/com/autotune/common/datasource/DataSourceInfo.java +++ b/src/main/java/com/autotune/common/datasource/DataSourceInfo.java @@ -44,7 +44,7 @@ public class DataSourceInfo { private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(DataSourceInfo.class); - public DataSourceInfo(String name, String provider, String serviceName, String namespace, URL url) { + public DataSourceInfo(String name, String provider, String serviceName, String namespace, URL url, AuthenticationConfig authConfig) { this.name = name; this.provider = provider; if (null == url) { @@ -54,6 +54,7 @@ public DataSourceInfo(String name, String provider, String serviceName, String n } this.serviceName = serviceName; this.namespace = namespace; + this.authenticationConfig = authConfig; } /** @@ -127,6 +128,24 @@ public void setAuthenticationConfig(AuthenticationConfig authenticationConfig) { this.authenticationConfig = authenticationConfig; } + // Method to check if the authentication details have changed + public boolean hasAuthChanged(AuthenticationConfig newAuthConfig) { + // Handle cases where one of the auth configs is missing or 'noAuth' + if (this.authenticationConfig == null) { + this.authenticationConfig = AuthenticationConfig.noAuth(); + } + if (newAuthConfig == null) { + newAuthConfig = AuthenticationConfig.noAuth(); + } + // Compare the authentication configs + return !this.authenticationConfig.equals(newAuthConfig); + } + + public void updateAuthConfig(AuthenticationConfig newAuthConfig) { + this.setAuthenticationConfig(newAuthConfig); + LOGGER.debug("Authentication details for datasource {} have been updated.", this.name); + } + @Override public String toString() { return "DataSourceInfo{" + @@ -135,7 +154,6 @@ public String toString() { ", serviceName='" + serviceName + '\'' + ", namespace='" + namespace + '\'' + ", url=" + url + - ", authenticationConfig=" + authenticationConfig + '}'; } } diff --git a/src/main/java/com/autotune/common/datasource/DataSourceOperatorImpl.java b/src/main/java/com/autotune/common/datasource/DataSourceOperatorImpl.java index ab3f5d535..fb1e98dbb 100644 --- a/src/main/java/com/autotune/common/datasource/DataSourceOperatorImpl.java +++ b/src/main/java/com/autotune/common/datasource/DataSourceOperatorImpl.java @@ -65,7 +65,7 @@ public static DataSourceInfo getMonitoringAgent(String dataSource) throws Monito monitoringAgentEndpoint = getServiceEndpoint(KruizeDeploymentInfo.monitoring_service); } if (dataSource.equals(AnalyzerConstants.PROMETHEUS_DATA_SOURCE)) { - monitoringAgent = new DataSourceInfo(KruizeDeploymentInfo.monitoring_agent, AnalyzerConstants.PROMETHEUS_DATA_SOURCE, null, null, new URL(monitoringAgentEndpoint)); + monitoringAgent = new DataSourceInfo(KruizeDeploymentInfo.monitoring_agent, AnalyzerConstants.PROMETHEUS_DATA_SOURCE, null, null, new URL(monitoringAgentEndpoint), null); } } diff --git a/src/main/java/com/autotune/database/dao/ExperimentDAO.java b/src/main/java/com/autotune/database/dao/ExperimentDAO.java index 039ca641a..a2fcab3f0 100644 --- a/src/main/java/com/autotune/database/dao/ExperimentDAO.java +++ b/src/main/java/com/autotune/database/dao/ExperimentDAO.java @@ -29,7 +29,7 @@ public interface ExperimentDAO { public ValidationOutputData addMetricProfileToDB(KruizeMetricProfileEntry kruizeMetricProfileEntry); // Add DataSource to DB - ValidationOutputData addDataSourceToDB(KruizeDataSourceEntry kruizeDataSourceEntry); + ValidationOutputData addDataSourceToDB(KruizeDataSourceEntry kruizeDataSourceEntry, ValidationOutputData validationOutputData); // Update experiment status public boolean updateExperimentStatus(KruizeObject kruizeObject, AnalyzerConstants.ExperimentStatus status); @@ -104,4 +104,6 @@ public interface ExperimentDAO { // Delete metadata public ValidationOutputData deleteKruizeDSMetadataEntryByName(String dataSourceName); + + ValidationOutputData addAuthenticationDetailsToDB(KruizeAuthenticationEntry kruizeAuthenticationEntry); } diff --git a/src/main/java/com/autotune/database/dao/ExperimentDAOImpl.java b/src/main/java/com/autotune/database/dao/ExperimentDAOImpl.java index 7b72baf77..ac37fc50e 100644 --- a/src/main/java/com/autotune/database/dao/ExperimentDAOImpl.java +++ b/src/main/java/com/autotune/database/dao/ExperimentDAOImpl.java @@ -444,15 +444,17 @@ public ValidationOutputData addMetricProfileToDB(KruizeMetricProfileEntry kruize /** * @param kruizeDataSourceEntry + * @param validationOutputData * @return validationOutputData contains the status of the DB insert operation */ @Override - public ValidationOutputData addDataSourceToDB(KruizeDataSourceEntry kruizeDataSourceEntry) { - ValidationOutputData validationOutputData = new ValidationOutputData(false, null, null); + public ValidationOutputData addDataSourceToDB(KruizeDataSourceEntry kruizeDataSourceEntry, ValidationOutputData validationOutputData) { Transaction tx = null; try (Session session = KruizeHibernateUtil.getSessionFactory().openSession()) { try { tx = session.beginTransaction(); + KruizeAuthenticationEntry kruizeAuthenticationEntry = session.get(KruizeAuthenticationEntry.class, validationOutputData.getAuthEntryId()); + kruizeDataSourceEntry.setKruizeAuthenticationEntry(kruizeAuthenticationEntry); session.persist(kruizeDataSourceEntry); tx.commit(); validationOutputData.setSuccess(true); @@ -500,6 +502,38 @@ public ValidationOutputData addMetadataToDB(KruizeDSMetadataEntry kruizeDSMetada return validationOutputData; } + /** + * @param kruizeAuthenticationEntry + * @return + */ + @Override + public ValidationOutputData addAuthenticationDetailsToDB(KruizeAuthenticationEntry kruizeAuthenticationEntry) { + LOGGER.info("kruizeAuthenticationEntry: {}", kruizeAuthenticationEntry); + ValidationOutputData validationOutputData = new ValidationOutputData(false, null, null); + Transaction tx = null; + Long authId; + try (Session session = KruizeHibernateUtil.getSessionFactory().openSession()) { + try { + tx = session.beginTransaction(); + session.persist(kruizeAuthenticationEntry); + tx.commit(); + validationOutputData.setSuccess(true); + validationOutputData.setAuthEntryId(kruizeAuthenticationEntry.getId()); + } catch (HibernateException e) { + LOGGER.error("Unable to save auth details: {}", e.getMessage()); + if (tx != null) tx.rollback(); + e.printStackTrace(); + validationOutputData.setSuccess(false); + validationOutputData.setMessage(e.getMessage()); + //todo save error to API_ERROR_LOG + } + } catch (Exception e) { + e.printStackTrace(); + LOGGER.error("Unable to save auth details: {}", e.getMessage()); + validationOutputData.setMessage(e.getMessage()); + } + return validationOutputData; + } @Override public boolean updateExperimentStatus(KruizeObject kruizeObject, AnalyzerConstants.ExperimentStatus status) { diff --git a/src/main/java/com/autotune/database/helper/DBHelpers.java b/src/main/java/com/autotune/database/helper/DBHelpers.java index 8b3d018fd..0ebbd0acc 100644 --- a/src/main/java/com/autotune/database/helper/DBHelpers.java +++ b/src/main/java/com/autotune/database/helper/DBHelpers.java @@ -28,8 +28,8 @@ import com.autotune.analyzer.serviceObjects.*; import com.autotune.analyzer.utils.AnalyzerConstants; import com.autotune.analyzer.utils.AnalyzerErrorConstants; -import com.autotune.analyzer.utils.ExperimentTypeUtil; import com.autotune.analyzer.utils.GsonUTCDateAdapter; +import com.autotune.common.auth.AuthenticationConfig; import com.autotune.common.data.dataSourceMetadata.*; import com.autotune.common.data.result.ContainerData; import com.autotune.common.data.result.ExperimentResultData; @@ -47,6 +47,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -822,13 +823,28 @@ public static List convertKruizeDataSourceToDataSourceObject(Lis int failureCount = 0; for (KruizeDataSourceEntry kruizeDataSource : kruizeDataSourceList) { try { - DataSourceInfo dataSourceInfo = null; + DataSourceInfo dataSourceInfo; + AuthenticationConfig authConfig = null; + if (kruizeDataSource.getKruizeAuthenticationEntry() != null) { + try { + JsonNode credentialsNode = kruizeDataSource.getKruizeAuthenticationEntry().getCredentials(); + String authType = kruizeDataSource.getKruizeAuthenticationEntry().getAuthenticationType(); + // Parse the JsonNode credentials into a JSONObject + JSONObject credentialsJson = new JSONObject(credentialsNode.toString()); + JSONObject authJson = new JSONObject() + .put(KruizeConstants.AuthenticationConstants.AUTHENTICATION_TYPE, authType) + .put(KruizeConstants.AuthenticationConstants.AUTHENTICATION_CREDENTIALS, credentialsJson); + authConfig = AuthenticationConfig.createAuthenticationConfigObject(authJson); + } catch (Exception e) { + LOGGER.debug("GSON failed to convert the DB Json object in convertKruizeDataSourceToDataSourceObject"); + } + } if (kruizeDataSource.getServiceName().isEmpty() && null != kruizeDataSource.getUrl()) { dataSourceInfo = new DataSourceInfo(kruizeDataSource.getName(), kruizeDataSource - .getProvider(), null, null, new URL(kruizeDataSource.getUrl())); + .getProvider(), null, null, new URL(kruizeDataSource.getUrl()), authConfig); } else{ dataSourceInfo = new DataSourceInfo(kruizeDataSource.getName(), kruizeDataSource - .getProvider(), kruizeDataSource.getServiceName(), kruizeDataSource.getNamespace(), null); + .getProvider(), kruizeDataSource.getServiceName(), kruizeDataSource.getNamespace(), null, authConfig); } dataSourceInfoList.add(dataSourceInfo); } catch (Exception e) { @@ -850,6 +866,7 @@ public static List convertKruizeDataSourceToDataSourceObject(Lis */ public static KruizeDataSourceEntry convertDataSourceToDataSourceDBObj(DataSourceInfo dataSourceInfo) { KruizeDataSourceEntry kruizeDataSource; + KruizeAuthenticationEntry kruizeAuthenticationEntry; try { kruizeDataSource = new KruizeDataSourceEntry(); kruizeDataSource.setVersion(KruizeConstants.DataSourceConstants.DataSourceMetadataInfoConstants.version); @@ -1077,6 +1094,29 @@ public static List convertDataSourceMetadataToMetadataObj return kruizeMetadataList; } + public static KruizeAuthenticationEntry convertAuthDetailsToAuthDetailsDBObj(AuthenticationConfig authenticationConfig, String serviceType) { + KruizeAuthenticationEntry kruizeAuthenticationEntry; + try { + kruizeAuthenticationEntry = new KruizeAuthenticationEntry(); + kruizeAuthenticationEntry.setAuthenticationType(authenticationConfig.getType().toString()); + // set the authentication details + String credentialsString = new Gson().toJson(authenticationConfig.getCredentials()); + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode credentials; + try { + credentials = objectMapper.readTree(credentialsString); + } catch (JsonProcessingException e) { + throw new Exception("Error occurred while creating credentials object : " + e.getMessage()); + } + kruizeAuthenticationEntry.setCredentials(credentials); + kruizeAuthenticationEntry.setServiceType(serviceType); + } catch (Exception e) { + kruizeAuthenticationEntry = null; + LOGGER.error("Error while converting Auth details Object to KruizeAuthentication table : {}", e.getMessage()); + e.printStackTrace(); + } + return kruizeAuthenticationEntry; + } } } diff --git a/src/main/java/com/autotune/database/init/KruizeHibernateUtil.java b/src/main/java/com/autotune/database/init/KruizeHibernateUtil.java index e518bde28..554cb051a 100644 --- a/src/main/java/com/autotune/database/init/KruizeHibernateUtil.java +++ b/src/main/java/com/autotune/database/init/KruizeHibernateUtil.java @@ -60,6 +60,7 @@ public static void buildSessionFactory() { configuration.addAnnotatedClass(KruizeDataSourceEntry.class); configuration.addAnnotatedClass(KruizeDSMetadataEntry.class); configuration.addAnnotatedClass(KruizeMetricProfileEntry.class); + configuration.addAnnotatedClass(KruizeAuthenticationEntry.class); } LOGGER.info("DB is trying to connect to {}", connectionURL); sfTemp = configuration.buildSessionFactory(); diff --git a/src/main/java/com/autotune/database/service/ExperimentDBService.java b/src/main/java/com/autotune/database/service/ExperimentDBService.java index 270bff3c1..765438b60 100644 --- a/src/main/java/com/autotune/database/service/ExperimentDBService.java +++ b/src/main/java/com/autotune/database/service/ExperimentDBService.java @@ -23,6 +23,7 @@ import com.autotune.analyzer.performanceProfiles.utils.PerformanceProfileUtil; import com.autotune.analyzer.serviceObjects.*; import com.autotune.analyzer.utils.AnalyzerConstants; +import com.autotune.common.auth.AuthenticationConfig; import com.autotune.common.data.ValidationOutputData; import com.autotune.common.data.dataSourceMetadata.DataSourceMetadataInfo; import com.autotune.common.data.result.ExperimentResultData; @@ -445,19 +446,30 @@ public List getExperimentResultData(String experiment_name * adds datasource to database table * * @param dataSourceInfo DataSourceInfo object + * @param validationOutputData contains validation data * @return ValidationOutputData object */ - public ValidationOutputData addDataSourceToDB(DataSourceInfo dataSourceInfo) { - ValidationOutputData validationOutputData = new ValidationOutputData(false, null, null); + public ValidationOutputData addDataSourceToDB(DataSourceInfo dataSourceInfo, ValidationOutputData validationOutputData) { try { KruizeDataSourceEntry kruizeDataSource = DBHelpers.Converters.KruizeObjectConverters.convertDataSourceToDataSourceDBObj(dataSourceInfo); - validationOutputData = this.experimentDAO.addDataSourceToDB(kruizeDataSource); + validationOutputData = this.experimentDAO.addDataSourceToDB(kruizeDataSource, validationOutputData); } catch (Exception e) { LOGGER.error("Not able to save data source due to {}", e.getMessage()); } return validationOutputData; } + public ValidationOutputData addAuthenticationDetailsToDB(AuthenticationConfig authenticationConfig, String serviceType) { + ValidationOutputData validationOutputData = new ValidationOutputData(false, null, null); + try { + KruizeAuthenticationEntry kruizeAuthenticationEntry = DBHelpers.Converters.KruizeObjectConverters.convertAuthDetailsToAuthDetailsDBObj(authenticationConfig, serviceType); + validationOutputData = this.experimentDAO.addAuthenticationDetailsToDB(kruizeAuthenticationEntry); + } catch (Exception e) { + LOGGER.error("Unable to save authentication details: {}", e.getMessage()); + } + return validationOutputData; + } + /** * fetches datasource with specified name from database * diff --git a/src/main/java/com/autotune/database/table/KruizeAuthenticationEntry.java b/src/main/java/com/autotune/database/table/KruizeAuthenticationEntry.java new file mode 100644 index 000000000..9cddb6f47 --- /dev/null +++ b/src/main/java/com/autotune/database/table/KruizeAuthenticationEntry.java @@ -0,0 +1,62 @@ +package com.autotune.database.table; + +import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.*; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +@Entity +@Table(name = "kruize_authentication") +public class KruizeAuthenticationEntry { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "authentication_type") + private String authenticationType; + + @JdbcTypeCode(SqlTypes.JSON) + private JsonNode credentials; + + @Column(name = "service_type") + private String serviceType; // e.g., "datasource", "cloudwatch", etc. + + public KruizeAuthenticationEntry(String authenticationType, JsonNode credentials, String serviceType) { + this.authenticationType = authenticationType; + this.credentials = credentials; + this.serviceType = serviceType; + } + + public KruizeAuthenticationEntry() { + } + + public Long getId() { + return id; + } + + // Getters and Setters for all fields + public String getServiceType() { + return serviceType; + } + + public void setServiceType(String serviceType) { + this.serviceType = serviceType; + } + + public String getAuthenticationType() { + return authenticationType; + } + + public void setAuthenticationType(String authenticationType) { + this.authenticationType = authenticationType; + } + + public JsonNode getCredentials() { + return credentials; + } + + public void setCredentials(JsonNode credentials) { + this.credentials = credentials; + } +} diff --git a/src/main/java/com/autotune/database/table/KruizeDataSourceEntry.java b/src/main/java/com/autotune/database/table/KruizeDataSourceEntry.java index 63e5a90d5..5f48a1e01 100644 --- a/src/main/java/com/autotune/database/table/KruizeDataSourceEntry.java +++ b/src/main/java/com/autotune/database/table/KruizeDataSourceEntry.java @@ -42,6 +42,17 @@ public class KruizeDataSourceEntry { private String serviceName; private String namespace; private String url; + @ManyToOne(cascade = CascadeType.PERSIST) // Cascade PERSIST to auto-save authentication entry + @JoinColumn(name = "authentication_id", nullable = false) // Foreign key column in the datasource table + private KruizeAuthenticationEntry kruizeAuthenticationEntry; + + public KruizeAuthenticationEntry getKruizeAuthenticationEntry() { + return kruizeAuthenticationEntry; + } + + public void setKruizeAuthenticationEntry(KruizeAuthenticationEntry kruizeAuthenticationEntry) { + this.kruizeAuthenticationEntry = kruizeAuthenticationEntry; + } public String getVersion() { return version; diff --git a/src/main/java/com/autotune/utils/KruizeConstants.java b/src/main/java/com/autotune/utils/KruizeConstants.java index 5e07d3142..8e8cf0a1e 100644 --- a/src/main/java/com/autotune/utils/KruizeConstants.java +++ b/src/main/java/com/autotune/utils/KruizeConstants.java @@ -422,20 +422,26 @@ private DataSourceDetailsInfoConstants() { } public static class DataSourceInfoMsgs { - public static final String ADDING_DATASOURCE = "Trying to add the datasource to collection: "; - public static final String VERIFYING_DATASOURCE_REACHABILITY = "Verifying datasource reachability status: "; + public static final String ADDING_DATASOURCE = "Trying to add the datasource to collection: {}"; + public static final String VERIFYING_DATASOURCE_REACHABILITY = "Verifying datasource reachability status: {}"; public static final String CHECKING_AVAILABLE_DATASOURCE = "Checking available datasources:"; public static final String CHECKING_AVAILABLE_DATASOURCE_FROM_DB = "Checking available datasources from database:"; public static final String NO_DATASOURCE_FOUND_IN_DB = "No datasource found in database."; - + public static final String CHECK_DATASOURCE_UPDATES = "Datasource {} already exists, Checking for updates..."; + public static final String DATASOURCE_AUTH_CHANGED = "Authentication details for datasource {} have changed. Checking if the datasource is serviceable with the new config..."; + public static final String DATASOURCE_AUTH_UNCHANGED= "No changes detected in the authentication details for datasource {}"; private DataSourceInfoMsgs() { } - } + } public static class DataSourceSuccessMsgs { + public static final String DATASOURCE_ADDED = "Datasource added to the collection successfully."; + public static final String DATASOURCE_ADDED_DB = "Datasource added to the database successfully."; public static final String DATASOURCE_FOUND = "Datasource found: "; public static final String DATASOURCE_SERVICEABLE = "Datasource is serviceable."; + public static final String DATASOURCE_AUTH_ADDED_DB = "Auth details added to the DB successfully."; + public static final String DATASOURCE_AUTH_UPDATED_DB = "Auth details updated in the DB successfully."; private DataSourceSuccessMsgs() { } @@ -460,6 +466,10 @@ public static class DataSourceErrorMsgs { public static final String ENDPOINT_NOT_FOUND = "Service endpoint not found."; public static final String MISSING_DATASOURCE_INFO = "Datasource is missing, add a valid Datasource"; public static final String INVALID_DATASOURCE_INFO = "Datasource is either missing or is invalid: "; + public static final String MISSING_DATASOURCE_AUTH = "Auth details are missing for datasource: {}"; + public static final String DATASOURCE_AUTH_DB_INSERTION_FAILED = "Failed to add auth details to DB: {}"; + public static final String DATASOURCE_AUTH_DB_UPDATE_FAILED = "Failed to update auth details in the DB: {}"; + public static final String DATASOURCE_AUTH_UPDATE_INVALID = "The updated authentication configuration is invalid. Reverting to the previous configuration."; private DataSourceErrorMsgs() { } diff --git a/src/main/java/com/autotune/utils/TrialHelpers.java b/src/main/java/com/autotune/utils/TrialHelpers.java index 78cb6b430..8760335d0 100644 --- a/src/main/java/com/autotune/utils/TrialHelpers.java +++ b/src/main/java/com/autotune/utils/TrialHelpers.java @@ -145,7 +145,7 @@ public static ExperimentTrial createDefaultExperimentTrial(int trialNumber, trialNumber, trialResultUrl.toString()); - DataSourceInfo datasourceInfo = new DataSourceInfo(KruizeDeploymentInfo.monitoring_agent, KruizeConstants.SupportedDatasources.PROMETHEUS, null, null, new URL(KruizeDeploymentInfo.monitoring_agent_endpoint)); + DataSourceInfo datasourceInfo = new DataSourceInfo(KruizeDeploymentInfo.monitoring_agent, KruizeConstants.SupportedDatasources.PROMETHEUS, null, null, new URL(KruizeDeploymentInfo.monitoring_agent_endpoint), null); HashMap datasourceInfoHashMap = new HashMap<>(); datasourceInfoHashMap.put(KruizeDeploymentInfo.monitoring_agent, datasourceInfo); //Change key value as per YAML input DeploymentTracking deploymentTracking = new DeploymentTracking();