Skip to content
Snippets Groups Projects
Commit b2ec6562 authored by Martin Weise's avatar Martin Weise
Browse files

Fixed the connection pool for database service

parent d1b9daef
No related branches found
No related tags found
2 merge requests!81New stable release,!80Multiple features connected with user management and ownership of databases
Showing
with 1132 additions and 165 deletions
...@@ -324,7 +324,11 @@ ...@@ -324,7 +324,11 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"metadata": {}, "metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [], "outputs": [],
"source": [] "source": []
} }
......
...@@ -893,7 +893,11 @@ ...@@ -893,7 +893,11 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"metadata": {}, "metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [], "outputs": [],
"source": [] "source": []
} }
......
This diff is collapsed.
...@@ -8,7 +8,6 @@ import at.tuwien.entities.database.Database; ...@@ -8,7 +8,6 @@ import at.tuwien.entities.database.Database;
import at.tuwien.exception.*; import at.tuwien.exception.*;
import at.tuwien.mapper.DatabaseMapper; import at.tuwien.mapper.DatabaseMapper;
import at.tuwien.service.MessageQueueService; import at.tuwien.service.MessageQueueService;
import at.tuwien.service.QueryStoreService;
import at.tuwien.service.impl.MariaDbServiceImpl; import at.tuwien.service.impl.MariaDbServiceImpl;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.security.SecurityRequirement;
...@@ -34,15 +33,13 @@ public class ContainerDatabaseEndpoint { ...@@ -34,15 +33,13 @@ public class ContainerDatabaseEndpoint {
private final DatabaseMapper databaseMapper; private final DatabaseMapper databaseMapper;
private final MariaDbServiceImpl databaseService; private final MariaDbServiceImpl databaseService;
private final QueryStoreService queryStoreService;
private final MessageQueueService messageQueueService; private final MessageQueueService messageQueueService;
@Autowired @Autowired
public ContainerDatabaseEndpoint(DatabaseMapper databaseMapper, MariaDbServiceImpl databaseService, public ContainerDatabaseEndpoint(DatabaseMapper databaseMapper, MariaDbServiceImpl databaseService,
QueryStoreService queryStoreService, MessageQueueService messageQueueService) { MessageQueueService messageQueueService) {
this.databaseMapper = databaseMapper; this.databaseMapper = databaseMapper;
this.databaseService = databaseService; this.databaseService = databaseService;
this.queryStoreService = queryStoreService;
this.messageQueueService = messageQueueService; this.messageQueueService = messageQueueService;
} }
...@@ -78,11 +75,10 @@ public class ContainerDatabaseEndpoint { ...@@ -78,11 +75,10 @@ public class ContainerDatabaseEndpoint {
@Valid @RequestBody DatabaseCreateDto createDto, @Valid @RequestBody DatabaseCreateDto createDto,
Principal principal) Principal principal)
throws ImageNotSupportedException, ContainerNotFoundException, DatabaseMalformedException, throws ImageNotSupportedException, ContainerNotFoundException, DatabaseMalformedException,
AmqpException, ContainerConnectionException, UserNotFoundException, QueryStoreException, AmqpException, ContainerConnectionException, UserNotFoundException,
DatabaseNotFoundException, DatabaseNameExistsException { DatabaseNotFoundException, DatabaseNameExistsException, DatabaseConnectionException {
final Database database = databaseService.create(containerId, createDto, principal); final Database database = databaseService.create(containerId, createDto, principal);
messageQueueService.createExchange(database, principal); messageQueueService.createExchange(database, principal);
queryStoreService.create(containerId, database.getId());
return ResponseEntity.status(HttpStatus.CREATED) return ResponseEntity.status(HttpStatus.CREATED)
.body(databaseMapper.databaseToDatabaseBriefDto(database)); .body(databaseMapper.databaseToDatabaseBriefDto(database));
} }
...@@ -128,7 +124,7 @@ public class ContainerDatabaseEndpoint { ...@@ -128,7 +124,7 @@ public class ContainerDatabaseEndpoint {
@NotBlank @PathVariable Long databaseId, @NotBlank @PathVariable Long databaseId,
Principal principal) throws DatabaseNotFoundException, Principal principal) throws DatabaseNotFoundException,
ImageNotSupportedException, DatabaseMalformedException, AmqpException, ContainerConnectionException, ImageNotSupportedException, DatabaseMalformedException, AmqpException, ContainerConnectionException,
ContainerNotFoundException { ContainerNotFoundException, DatabaseConnectionException {
final Database database = databaseService.findById(containerId, databaseId); final Database database = databaseService.findById(containerId, databaseId);
messageQueueService.deleteExchange(database); messageQueueService.deleteExchange(database);
databaseService.delete(containerId, databaseId, principal); databaseService.delete(containerId, databaseId, principal);
......
-- SEQUENCES
CREATE SEQUENCE `qs_tables_seq`;
CREATE SEQUENCE `qs_columns_seq`;
CREATE SEQUENCE `qs_columns_seq`;
-- TABLES
CREATE TABLE `qs_queries`
(
`id` bigint not null primary key default nextval(`qs_queries_seq`),
`cid` bigint not null,
`created` datetime not null,
`created_by` bigint not null,
`dbid` bigint not null,
`execution` datetime not null,
`last_modified` datetime not null,
`query` text not null,
`query_hash` varchar(255) not null,
`result_hash` varchar(255),
`result_number` bigint
);
CREATE TABLE `qs_tables`
(
`id` bigint not null primary key default nextval(`qs_tables_seq`),
`created` datetime not null,
`dbid` bigint not null,
`last_modified` datetime
);
CREATE TABLE `qs_columns`
(
`id` bigint not null primary key default nextval(`qs_columns_seq`),
`created` datetime not null,
`dbid` bigint not null,
`tid` bigint not null,
`last_modified` datetime
);
\ No newline at end of file
...@@ -7,6 +7,7 @@ import at.tuwien.api.database.LanguageTypeDto; ...@@ -7,6 +7,7 @@ import at.tuwien.api.database.LanguageTypeDto;
import at.tuwien.api.user.UserDetailsDto; import at.tuwien.api.user.UserDetailsDto;
import at.tuwien.entities.database.Database; import at.tuwien.entities.database.Database;
import at.tuwien.entities.database.LanguageType; import at.tuwien.entities.database.LanguageType;
import at.tuwien.querystore.Query;
import org.apache.http.auth.BasicUserPrincipal; import org.apache.http.auth.BasicUserPrincipal;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
......
...@@ -63,7 +63,7 @@ public interface DatabaseService { ...@@ -63,7 +63,7 @@ public interface DatabaseService {
*/ */
void delete(Long id, Long databaseId, Principal principal) void delete(Long id, Long databaseId, Principal principal)
throws DatabaseNotFoundException, ImageNotSupportedException, throws DatabaseNotFoundException, ImageNotSupportedException,
DatabaseMalformedException, AmqpException, ContainerConnectionException, ContainerNotFoundException; DatabaseMalformedException, AmqpException, ContainerConnectionException, ContainerNotFoundException, DatabaseConnectionException;
/** /**
* Creates a new database with minimal metadata in the metadata database and creates a new database on the container. * Creates a new database with minimal metadata in the metadata database and creates a new database on the container.
...@@ -80,7 +80,7 @@ public interface DatabaseService { ...@@ -80,7 +80,7 @@ public interface DatabaseService {
*/ */
Database create(Long id, DatabaseCreateDto createDto, Principal principal) Database create(Long id, DatabaseCreateDto createDto, Principal principal)
throws ImageNotSupportedException, ContainerNotFoundException, throws ImageNotSupportedException, ContainerNotFoundException,
DatabaseMalformedException, AmqpException, ContainerConnectionException, UserNotFoundException, DatabaseNameExistsException; DatabaseMalformedException, AmqpException, ContainerConnectionException, UserNotFoundException, DatabaseNameExistsException, DatabaseConnectionException;
/** /**
* Updates the database metadata. * Updates the database metadata.
......
package at.tuwien.service;
import at.tuwien.exception.DatabaseNotFoundException;
import at.tuwien.exception.ImageNotSupportedException;
import at.tuwien.exception.QueryStoreException;
public interface QueryStoreService {
/**
* Creates the query store by executing a query, the Hibernate session is configured to automatically create the necessary table.
*
* @param containerId The container id.
* @param databaseId The database id.
* @throws DatabaseNotFoundException The database was not found in the metadata database.
* @throws ImageNotSupportedException The image is not supported, currently we only support MariaDB.
* @throws QueryStoreException The query store failed to create.
*/
void create(Long containerId, Long databaseId) throws DatabaseNotFoundException,
ImageNotSupportedException, QueryStoreException;
}
...@@ -5,18 +5,18 @@ import at.tuwien.entities.container.image.ContainerImage; ...@@ -5,18 +5,18 @@ import at.tuwien.entities.container.image.ContainerImage;
import at.tuwien.entities.container.image.ContainerImageEnvironmentItem; import at.tuwien.entities.container.image.ContainerImageEnvironmentItem;
import at.tuwien.entities.container.image.ContainerImageEnvironmentItemType; import at.tuwien.entities.container.image.ContainerImageEnvironmentItemType;
import at.tuwien.entities.database.Database; import at.tuwien.entities.database.Database;
import at.tuwien.querystore.Column; import at.tuwien.exception.DatabaseConnectionException;
import at.tuwien.querystore.Query; import com.mchange.v2.c3p0.ComboPooledDataSource;
import at.tuwien.querystore.Table;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.query.NativeQuery; import org.hibernate.query.NativeQuery;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.PersistenceException; import javax.persistence.PersistenceException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
...@@ -24,54 +24,68 @@ import java.util.stream.Collectors; ...@@ -24,54 +24,68 @@ import java.util.stream.Collectors;
@Service @Service
public abstract class HibernateConnector { public abstract class HibernateConnector {
protected static Session getCurrentSession(ContainerImage image, Container container) { protected static Connection getConnection(ContainerImage image, Container container) throws DatabaseConnectionException {
return getCurrentSession(image, container, null); return getConnection(image, container, null);
} }
protected static Session getCurrentSession(ContainerImage image, Container container, Database database) { protected static Connection getConnection(ContainerImage image, Container container, Database database) throws DatabaseConnectionException {
final ComboPooledDataSource dataSource = new ComboPooledDataSource();
final String url = "jdbc:" + image.getJdbcMethod() + "://" + container.getInternalName() + "/" + (database != null ? database.getInternalName() : ""); final String url = "jdbc:" + image.getJdbcMethod() + "://" + container.getInternalName() + "/" + (database != null ? database.getInternalName() : "");
dataSource.setJdbcUrl(url);
final String username = image.getEnvironment() final String username = image.getEnvironment()
.stream() .stream()
.filter(e -> e.getType().equals(ContainerImageEnvironmentItemType.PRIVILEGED_USERNAME)) .filter(e -> e.getType().equals(ContainerImageEnvironmentItemType.PRIVILEGED_USERNAME))
.map(ContainerImageEnvironmentItem::getValue) .map(ContainerImageEnvironmentItem::getValue)
.collect(Collectors.toList()) .collect(Collectors.toList())
.get(0); .get(0);
dataSource.setUser(username);
final String password = image.getEnvironment() final String password = image.getEnvironment()
.stream() .stream()
.filter(e -> e.getType().equals(ContainerImageEnvironmentItemType.PRIVILEGED_PASSWORD)) .filter(e -> e.getType().equals(ContainerImageEnvironmentItemType.PRIVILEGED_PASSWORD))
.map(ContainerImageEnvironmentItem::getValue) .map(ContainerImageEnvironmentItem::getValue)
.collect(Collectors.toList()) .collect(Collectors.toList())
.get(0); .get(0);
final Configuration config = new Configuration(); dataSource.setPassword(password);
config.configure("mariadb_hibernate.cfg.xml"); dataSource.setInitialPoolSize(5);
config.setProperty("hibernate.connection.url", url); dataSource.setMinPoolSize(5);
config.setProperty("hibernate.connection.username", username); dataSource.setAcquireIncrement(5);
config.setProperty("hibernate.connection.password", password); dataSource.setMaxPoolSize(20);
config.setProperty("hibernate.connection.driver_class", image.getDriverClass()); dataSource.setMaxStatements(100);
config.setProperty("hibernate.dialect", image.getDialect()); final Connection connection;
final SessionFactory sessionFactory = config.buildSessionFactory(); try {
Session session = sessionFactory.getCurrentSession(); connection = dataSource.getConnection();
if (!session.isOpen()) { } catch (SQLException e) {
log.warn("Session is closed, opening..."); log.error("Failed to connect to the database");
session = sessionFactory.openSession(); log.debug("failed to connect to the database {}", database);
throw new DatabaseConnectionException("Failed to connect to the database");
} }
return session; return connection;
} }
protected static Long activeConnection(Session session) { protected static Long activeConnection(Connection connection) throws DatabaseConnectionException {
final NativeQuery<?> nativeQuery = session.createSQLQuery("SHOW STATUS LIKE 'threads_connected'"); final ResultSet resultSet = execute(connection, "SHOW STATUS LIKE 'threads_connected'");
final List<?> result;
try { try {
result = nativeQuery.getResultList(); if (resultSet.next()) {
} catch (PersistenceException e) { return resultSet.getLong(2);
log.error("Failed to collect number of used connections"); }
/* ignore */ } catch (SQLException e) {
return null; log.error("Failed to determine active connections");
throw new DatabaseConnectionException("Failed to determine active connections", e);
} }
final Object[] row = (Object[]) result.get(0); log.error("Failed to determine active connections");
log.debug("current number of connections: {}", Long.parseLong(String.valueOf(row[1]))); throw new DatabaseConnectionException("Failed to determine active connections");
return Long.parseLong(String.valueOf(row[1]));
} }
protected static ResultSet execute(Connection connection, String statement) throws DatabaseConnectionException {
final PreparedStatement preparedStatement;
try {
preparedStatement = connection.prepareStatement(statement);
return preparedStatement.executeQuery();
} catch (SQLException e) {
log.error("Failed to execute statement");
log.debug("failed to execute statement {}", statement);
throw new DatabaseConnectionException("Failed to execute statement", e);
}
}
} }
...@@ -27,6 +27,9 @@ import org.springframework.transaction.annotation.Transactional; ...@@ -27,6 +27,9 @@ import org.springframework.transaction.annotation.Transactional;
import javax.persistence.PersistenceException; import javax.persistence.PersistenceException;
import java.security.Principal; import java.security.Principal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.time.Instant; import java.time.Instant;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
...@@ -101,29 +104,16 @@ public class MariaDbServiceImpl extends HibernateConnector implements DatabaseSe ...@@ -101,29 +104,16 @@ public class MariaDbServiceImpl extends HibernateConnector implements DatabaseSe
@Transactional @Transactional
public void delete(Long containerId, Long databaseId, Principal principal) throws DatabaseNotFoundException, public void delete(Long containerId, Long databaseId, Principal principal) throws DatabaseNotFoundException,
ImageNotSupportedException, DatabaseMalformedException, ContainerConnectionException, AmqpException, ImageNotSupportedException, DatabaseMalformedException, ContainerConnectionException, AmqpException,
ContainerNotFoundException { ContainerNotFoundException, DatabaseConnectionException {
final Container container = containerService.find(containerId); final Container container = containerService.find(containerId);
final Database database = findPublicOrMineById(containerId, databaseId, principal); final Database database = findPublicOrMineById(containerId, databaseId, principal);
if (!database.getContainer().getImage().getRepository().equals("mariadb")) { if (!database.getContainer().getImage().getRepository().equals("mariadb")) {
throw new ImageNotSupportedException("Currently only MariaDB is supported"); throw new ImageNotSupportedException("Currently only MariaDB is supported");
} }
/* run query */ /* run query */
final Session session = getCurrentSession(container.getImage(), container, database); final Connection connection = getConnection(container.getImage(), container, database);
final Transaction transaction = session.beginTransaction(); execute(connection, databaseMapper.databaseToRawDeleteDatabaseQuery(database));
final NativeQuery<?> query = session.createSQLQuery(databaseMapper.databaseToRawDeleteDatabaseQuery(database)); activeConnection(connection);
try {
log.debug("query affected {} rows", query.executeUpdate());
activeConnection(session);
transaction.commit();
} catch (ServiceException e) {
log.error("Failed to delete database.");
session.close();
throw new DatabaseMalformedException("Failed to delete database", e);
} finally {
if (session.isOpen()) {
session.close();
}
}
database.setDeleted(Instant.now()) /* method has void, only for debug logs */; database.setDeleted(Instant.now()) /* method has void, only for debug logs */;
/* save in metadata database */ /* save in metadata database */
databaseRepository.deleteById(databaseId); databaseRepository.deleteById(databaseId);
...@@ -135,7 +125,8 @@ public class MariaDbServiceImpl extends HibernateConnector implements DatabaseSe ...@@ -135,7 +125,8 @@ public class MariaDbServiceImpl extends HibernateConnector implements DatabaseSe
@Transactional @Transactional
public Database create(Long containerId, DatabaseCreateDto createDto, Principal principal) public Database create(Long containerId, DatabaseCreateDto createDto, Principal principal)
throws ImageNotSupportedException, ContainerNotFoundException, throws ImageNotSupportedException, ContainerNotFoundException,
DatabaseMalformedException, AmqpException, ContainerConnectionException, UserNotFoundException, DatabaseNameExistsException { DatabaseMalformedException, AmqpException, ContainerConnectionException, UserNotFoundException,
DatabaseNameExistsException, DatabaseConnectionException {
final Container container = containerService.find(containerId); final Container container = containerService.find(containerId);
if (container.getDatabases().size() != 0) { if (container.getDatabases().size() != 0) {
log.error("Currently we only support one database per container."); log.error("Currently we only support one database per container.");
...@@ -146,31 +137,13 @@ public class MariaDbServiceImpl extends HibernateConnector implements DatabaseSe ...@@ -146,31 +137,13 @@ public class MariaDbServiceImpl extends HibernateConnector implements DatabaseSe
database.setName(createDto.getName()); database.setName(createDto.getName());
database.setInternalName(databaseMapper.nameToInternalName(database.getName())); database.setInternalName(databaseMapper.nameToInternalName(database.getName()));
database.setContainer(container); database.setContainer(container);
/* run query */ /* create database */
final Session session = getCurrentSession(container.getImage(), container); final Connection connection = getConnection(container.getImage(), container);
final Transaction transaction = session.beginTransaction(); execute(connection, databaseMapper.databaseToRawCreateDatabaseQuery(database));
final NativeQuery<?> query = session.createSQLQuery(databaseMapper.databaseToRawCreateDatabaseQuery(database)); log.debug("active database connections {}", activeConnection(connection));
try { /* grant read-only access */
log.debug("query affected {} rows", query.executeUpdate()); execute(connection, databaseMapper.imageToRawGrantReadonlyAccessQuery());
} catch (PersistenceException e) { log.debug("active database connections {}", activeConnection(connection));
log.error("Failed to create database");
log.debug("failed to create database: {}", e.getMessage());
session.close();
throw new DatabaseNameExistsException("Failed to create database", e);
}
final NativeQuery<?> grant = session.createSQLQuery(databaseMapper.imageToRawGrantReadonlyAccessQuery());
try {
log.debug("grant affected {} rows", grant.executeUpdate());
activeConnection(session);
transaction.commit();
} catch (PersistenceException e) {
log.error("Failed to grant privileges.");
throw new DatabaseMalformedException("Failed to grant privileges", e);
} finally {
if (session.isOpen()) {
session.close();
}
}
/* save in metadata database */ /* save in metadata database */
database.setExchange(amqpMapper.exchangeName(database)); database.setExchange(amqpMapper.exchangeName(database));
database.setDescription(createDto.getDescription()); database.setDescription(createDto.getDescription());
......
package at.tuwien.service.impl;
import at.tuwien.entities.database.Database;
import at.tuwien.exception.DatabaseNotFoundException;
import at.tuwien.exception.ImageNotSupportedException;
import at.tuwien.exception.QueryStoreException;
import at.tuwien.service.DatabaseService;
import at.tuwien.service.QueryStoreService;
import lombok.extern.log4j.Log4j2;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.persistence.PersistenceException;
@Log4j2
@Service
public class QueryStoreServiceImpl extends HibernateConnector implements QueryStoreService {
private final DatabaseService databaseService;
@Autowired
public QueryStoreServiceImpl(DatabaseService databaseService) {
this.databaseService = databaseService;
}
@Override
public void create(Long containerId, Long databaseId) throws DatabaseNotFoundException,
ImageNotSupportedException, QueryStoreException {
/* find */
final Database database = databaseService.findById(containerId, databaseId);
if (!database.getContainer().getImage().getRepository().equals("mariadb")) {
throw new ImageNotSupportedException("Currently only MariaDB is supported");
}
/* run query */
final Session session = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database);
final Transaction transaction = session.beginTransaction();
/* use jpq to select all */
final org.hibernate.query.Query<at.tuwien.querystore.Query> queries = session.createQuery("select q from Query q",
at.tuwien.querystore.Query.class);
try {
queries.getResultList();
activeConnection(session);
transaction.commit();
} catch (PersistenceException e) {
log.error("Failed to find all queries");
session.close();
throw new QueryStoreException("Failed to find all queries");
} finally {
if (session.isOpen()) {
session.close();
}
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment