diff --git a/catalog-rest-service/src/main/java/org/openmetadata/catalog/exception/AirflowException.java b/catalog-rest-service/src/main/java/org/openmetadata/catalog/exception/AirflowException.java index 0d9a4308165..576dd0b2d95 100644 --- a/catalog-rest-service/src/main/java/org/openmetadata/catalog/exception/AirflowException.java +++ b/catalog-rest-service/src/main/java/org/openmetadata/catalog/exception/AirflowException.java @@ -16,7 +16,6 @@ package org.openmetadata.catalog.exception; import javax.ws.rs.core.Response; public class AirflowException extends WebServiceException { - private static final String BY_NAME_MESSAGE = "Airflow Exception [%s] due to [%s]."; public AirflowException(String message) { diff --git a/catalog-rest-service/src/main/java/org/openmetadata/catalog/exception/InvalidServiceConnectionException.java b/catalog-rest-service/src/main/java/org/openmetadata/catalog/exception/InvalidServiceConnectionException.java new file mode 100644 index 00000000000..7ce51ac6ed5 --- /dev/null +++ b/catalog-rest-service/src/main/java/org/openmetadata/catalog/exception/InvalidServiceConnectionException.java @@ -0,0 +1,27 @@ +package org.openmetadata.catalog.exception; + +import javax.ws.rs.core.Response; + +public class InvalidServiceConnectionException extends WebServiceException { + private static final String BY_NAME_MESSAGE = "InvalidServiceConnectionException for service [%s] due to [%s]."; + + public InvalidServiceConnectionException(String message) { + super(Response.Status.BAD_REQUEST, message); + } + + private InvalidServiceConnectionException(Response.Status status, String message) { + super(status, message); + } + + public static InvalidServiceConnectionException byMessage(String name, String errorMessage, Response.Status status) { + return new InvalidServiceConnectionException(status, buildMessageByName(name, errorMessage)); + } + + public static InvalidServiceConnectionException byMessage(String name, String errorMessage) { + return new InvalidServiceConnectionException(Response.Status.BAD_REQUEST, buildMessageByName(name, errorMessage)); + } + + private static String buildMessageByName(String name, String errorMessage) { + return String.format(BY_NAME_MESSAGE, name, errorMessage); + } +} diff --git a/catalog-rest-service/src/main/java/org/openmetadata/catalog/jdbi3/DatabaseServiceRepository.java b/catalog-rest-service/src/main/java/org/openmetadata/catalog/jdbi3/DatabaseServiceRepository.java index 0ee9519d60d..a677dc4e965 100644 --- a/catalog-rest-service/src/main/java/org/openmetadata/catalog/jdbi3/DatabaseServiceRepository.java +++ b/catalog-rest-service/src/main/java/org/openmetadata/catalog/jdbi3/DatabaseServiceRepository.java @@ -22,8 +22,10 @@ import java.util.ArrayList; import java.util.List; import java.util.UUID; import org.openmetadata.catalog.Entity; +import org.openmetadata.catalog.api.services.CreateDatabaseService; import org.openmetadata.catalog.api.services.DatabaseConnection; import org.openmetadata.catalog.entity.services.DatabaseService; +import org.openmetadata.catalog.exception.InvalidServiceConnectionException; import org.openmetadata.catalog.resources.services.database.DatabaseServiceResource; import org.openmetadata.catalog.type.ChangeDescription; import org.openmetadata.catalog.type.EntityReference; @@ -31,6 +33,7 @@ import org.openmetadata.catalog.type.Include; import org.openmetadata.catalog.type.Relationship; import org.openmetadata.catalog.util.EntityInterface; import org.openmetadata.catalog.util.EntityUtil.Fields; +import org.openmetadata.catalog.util.JsonUtils; public class DatabaseServiceRepository extends EntityRepository { private static final String UPDATE_FIELDS = "owner"; @@ -76,6 +79,8 @@ public class DatabaseServiceRepository extends EntityRepository public void prepare(DatabaseService databaseService) throws IOException { // Check if owner is valid and set the relationship databaseService.setOwner(Entity.getEntityReference(databaseService.getOwner())); + // validate database service connection + validateDatabaseConnection(databaseService.getConnection(), databaseService.getServiceType()); } @Override @@ -103,6 +108,21 @@ public class DatabaseServiceRepository extends EntityRepository return new DatabaseServiceUpdater(original, updated, operation); } + private void validateDatabaseConnection( + DatabaseConnection databaseConnection, CreateDatabaseService.DatabaseServiceType databaseServiceType) { + try { + Object connectionConfig = databaseConnection.getConfig(); + String clazzName = + "org.openmetadata.catalog.services.connections.database." + databaseServiceType.value() + "Connection"; + Class clazz = Class.forName(clazzName); + JsonUtils.convertValue(connectionConfig, clazz); + } catch (Exception e) { + throw InvalidServiceConnectionException.byMessage( + databaseServiceType.value(), + String.format("Failed to construct connection instance of %s", databaseServiceType.value())); + } + } + public static class DatabaseServiceEntityInterface extends EntityInterface { public DatabaseServiceEntityInterface(DatabaseService entity) { super(Entity.DATABASE_SERVICE, entity); diff --git a/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/services/database/DatabaseServiceResource.java b/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/services/database/DatabaseServiceResource.java index f55ebb95d2b..ba1b1ccf000 100644 --- a/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/services/database/DatabaseServiceResource.java +++ b/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/services/database/DatabaseServiceResource.java @@ -47,6 +47,7 @@ import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.UriInfo; import org.openmetadata.catalog.Entity; import org.openmetadata.catalog.api.services.CreateDatabaseService; +import org.openmetadata.catalog.api.services.DatabaseConnection; import org.openmetadata.catalog.entity.services.DatabaseService; import org.openmetadata.catalog.fernet.Fernet; import org.openmetadata.catalog.jdbi3.CollectionDAO; @@ -355,4 +356,18 @@ public class DatabaseServiceResource extends EntityResource clazz = Class.forName(clazzName); + JsonUtils.convertValue(connectionConfig, clazz); + } catch (Exception e) { + throw new RuntimeException( + String.format("Failed to construct connection instance of %s", databaseServiceType.value())); + } + } } diff --git a/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/services/ingestionpipelines/IngestionPipelineResource.java b/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/services/ingestionpipelines/IngestionPipelineResource.java index 9e45a4a60e0..deea923b0bd 100644 --- a/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/services/ingestionpipelines/IngestionPipelineResource.java +++ b/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/services/ingestionpipelines/IngestionPipelineResource.java @@ -29,6 +29,7 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; import java.io.IOException; import java.util.List; +import java.util.Locale; import java.util.UUID; import javax.json.JsonPatch; import javax.validation.Valid; @@ -437,7 +438,7 @@ public class IngestionPipelineResource extends EntityResource extends CatalogApplicationTest { createDatabaseService .withName("mysqlDB") - .withServiceType(DatabaseServiceType.MySQL) + .withServiceType(DatabaseServiceType.Mysql) .withConnection(TestUtils.MYSQL_DATABASE_CONNECTION); databaseService = databaseServiceResourceTest.createEntity(createDatabaseService, ADMIN_AUTH_HEADERS); MYSQL_REFERENCE = new DatabaseServiceEntityInterface(databaseService).getEntityReference(); diff --git a/catalog-rest-service/src/test/java/org/openmetadata/catalog/resources/services/DatabaseServiceResourceTest.java b/catalog-rest-service/src/test/java/org/openmetadata/catalog/resources/services/DatabaseServiceResourceTest.java index fb7560b6e5d..77a0489e004 100644 --- a/catalog-rest-service/src/test/java/org/openmetadata/catalog/resources/services/DatabaseServiceResourceTest.java +++ b/catalog-rest-service/src/test/java/org/openmetadata/catalog/resources/services/DatabaseServiceResourceTest.java @@ -78,7 +78,7 @@ public class DatabaseServiceResourceTest extends EntityResourceTest createEntity(create, ADMIN_AUTH_HEADERS), BAD_REQUEST, "connection must not be null"); @@ -115,6 +115,31 @@ public class DatabaseServiceResourceTest extends EntityResourceTest createEntity(createRequest(test).withDescription(null).withConnection(dbConn), ADMIN_AUTH_HEADERS), + BAD_REQUEST, + "InvalidServiceConnectionException for service [Snowflake] due to [Failed to construct connection instance of Snowflake]"); + DatabaseService service = createAndCheckEntity(createRequest(test).withDescription(null), ADMIN_AUTH_HEADERS); + // Update database description and ingestion service that are null + CreateDatabaseService update = createRequest(test).withDescription("description1"); + + ChangeDescription change = getChangeDescription(service.getVersion()); + change.getFieldsAdded().add(new FieldChange().withName("description").withNewValue("description1")); + updateAndCheckEntity(update, OK, ADMIN_AUTH_HEADERS, UpdateType.MINOR_UPDATE, change); + MysqlConnection mysqlConnection = new MysqlConnection().withHostPort("localhost:3300").withUsername("test"); + DatabaseConnection databaseConnection = new DatabaseConnection().withConfig(mysqlConnection); + update.withConnection(databaseConnection); + assertResponseContains( + () -> updateEntity(update, OK, ADMIN_AUTH_HEADERS), + BAD_REQUEST, + "InvalidServiceConnectionException for service [Snowflake] due to [Failed to construct connection instance of Snowflake]"); + } + @Test void put_addIngestion_as_admin_2xx(TestInfo test) throws IOException { DatabaseService service = createAndCheckEntity(createRequest(test).withDescription(null), ADMIN_AUTH_HEADERS); @@ -172,6 +197,11 @@ public class DatabaseServiceResourceTest extends EntityResourceTest