You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
395 lines
20 KiB
395 lines
20 KiB
package de.superx; |
|
|
|
import java.io.BufferedReader; |
|
import java.io.IOException; |
|
import java.io.InputStreamReader; |
|
import java.net.InetAddress; |
|
import java.net.UnknownHostException; |
|
import java.sql.Connection; |
|
import java.sql.DriverManager; |
|
import java.sql.ResultSet; |
|
import java.sql.SQLException; |
|
import java.sql.Statement; |
|
import java.text.SimpleDateFormat; |
|
import java.time.LocalDate; |
|
import java.time.LocalDateTime; |
|
import java.time.format.DateTimeFormatter; |
|
import java.util.Date; |
|
import java.util.HashMap; |
|
import java.util.Map; |
|
import java.util.Properties; |
|
import java.util.regex.Matcher; |
|
import java.util.regex.Pattern; |
|
import java.util.stream.Collectors; |
|
|
|
import javax.sql.DataSource; |
|
|
|
import org.junit.Ignore; |
|
|
|
import com.zaxxer.hikari.HikariDataSource; |
|
|
|
@Ignore |
|
public class TestDbManagerPg implements TestDbManager { |
|
// TODO: Hier sollten ALLE Test-DBs definiert werden |
|
// Momentan wird aber nur eduetl für die Tests verwendet |
|
protected static final String[] TEST_DBS = new String[] { EDUETL_DB, HISINONE_DB }; |
|
protected static final String LOCAL = "localhost"; |
|
protected static final String USER = "postgres"; |
|
protected static final String PASSWORD = "postgres"; |
|
private static final String QISDEV_HIS1_DUMP_URL = "http://qisdev.his.de/databaseBackups/hio_%s-stable,audit,develop,tests_qamuster.backup"; |
|
private static final String QISDEV_HIS1_DUMP_URL_BRANCH = "http://qisdev.his.de/databaseBackups/hio_%s-stable,audit_qamuster.backup"; |
|
private static final String QISDEV_EDUETL_DUMP_URL = "http://qisdev.his.de/Distributionen/BI-Musterdaten/%s/eduetl_muster_update.backup"; |
|
private static Pattern BRANCH_NAME_PATTERN = Pattern.compile("^(release\\_\\d\\d\\d\\d\\_\\d\\d)|(master)$"); |
|
private static final String VMPOSTGRES = "vmpostgres17.his.de"; |
|
private static final String SERVER = VMPOSTGRES; |
|
private static final String CONNECTION_STRING_PREFIX = String.format( |
|
"jdbc:postgresql://%s/", SERVER); |
|
|
|
private static final String PREFIX = "bi_tests_"; |
|
|
|
protected static final String CONNECTION_STRING = String.format("%spostgres", CONNECTION_STRING_PREFIX); |
|
|
|
private String branchName; |
|
private Map<String,String> testDbNames = new HashMap<>(); |
|
|
|
public TestDbManagerPg() { |
|
String branch = getBranchName(); |
|
System.out.println(String.format("Branch name über git gefunden: %s", branch)); |
|
} |
|
|
|
@Override |
|
public |
|
void setupTestDbs() { |
|
String connectionString = CONNECTION_STRING; |
|
Properties props = dbConnectionProperties(); |
|
try ( |
|
Connection conn = DriverManager.getConnection(connectionString, props); |
|
Statement stmt = conn.createStatement(); |
|
Statement deleteStmt = conn.createStatement(); |
|
) { |
|
|
|
// "Legacy": Dumps von den Builds auf |
|
// qa.his.de und qisdev.his.de holen: |
|
// ensureLatestQaDumps(); |
|
|
|
// "Neu": Dumps von qisdev direkt herunterladen: |
|
LocalDateTime ld = LocalDateTime.now().minusHours(19).minusMinutes(30); |
|
String dateStr = ld.format(DateTimeFormatter.BASIC_ISO_DATE); |
|
ensureLatestQisdevDumps(dateStr); |
|
// TODO: Hier sollte über ALLE Test-DBs iteriert werden |
|
// Momentan wird aber nur eduetl für die Tests verwendet |
|
for (String dbName : TEST_DBS) { |
|
String lookupSql = String.format( |
|
"SELECT datname FROM pg_database WHERE datname = '" + getTemplateName(dbName, branchName, dateStr) + "';"); |
|
ResultSet result = stmt.executeQuery(lookupSql); |
|
if (!result.next()) { |
|
throw new RuntimeException("could not set up test database, because no db template was found :´("); |
|
} |
|
String templateName = result.getString("datname"); |
|
String databaseName = getTestDbName(dbName); |
|
String createSql = String.format("DROP DATABASE IF EXISTS %s; CREATE DATABASE %s TEMPLATE %s;", databaseName, databaseName, templateName); |
|
stmt.execute(createSql); |
|
} |
|
} catch (SQLException e) { |
|
System.out.println("Schade, etwas ging bei der Datenbankanlage schief:"); |
|
e.printStackTrace(); |
|
} |
|
/* |
|
* workaround wegen vorübergehend "schräger" Musterdaten - zum Vergleich: |
|
* https://hiszilla.his.de/hiszilla/show_bug.cgi?id=364365 |
|
* wenn das Erfassungssystem auf Version 2026.06 ist und die Daten dort |
|
* angepasst wurden, _sollte_ der workaround entfernt werden können... |
|
*/ |
|
String eduetlName = getTestDbName(EDUETL_DB); |
|
connectionString = String.format("%s%s", CONNECTION_STRING_PREFIX, eduetlName); |
|
try ( |
|
Connection conn = DriverManager.getConnection(connectionString, props); |
|
Statement stmt = conn.createStatement(); |
|
) { |
|
String mySql = "UPDATE sos_stg_aggr SET fach_sem_zahl = abs(fach_sem_zahl);"; |
|
stmt.execute(mySql); |
|
} catch (SQLException e) { |
|
System.out.println("Beim Anpassungen der eduetl-Datenbank ging etwas schief:"); |
|
e.printStackTrace(); |
|
} |
|
// workaround Ende |
|
} |
|
|
|
@Override |
|
public DataSource getDataSource(String logicalDatabaseName) { |
|
HikariDataSource ds = new HikariDataSource(); |
|
ds.setJdbcUrl(getJdbcUrl(logicalDatabaseName)); |
|
ds.setUsername(TestDbManagerPg.USER); |
|
ds.setPassword(TestDbManagerPg.PASSWORD); |
|
ds.setConnectionInitSql("SET standard_conforming_strings TO 'off'; SET datestyle TO 'ISO, DMY';"); |
|
if ("hisinone".equals(logicalDatabaseName)) { |
|
ds.setSchema("hisinone"); |
|
} |
|
return ds; |
|
} |
|
|
|
@Override |
|
public void removeTestDbs() { |
|
String connectionString = TestDbManagerPg.CONNECTION_STRING; |
|
Properties props = dbConnectionProperties(); |
|
try ( |
|
Connection conn = DriverManager.getConnection(connectionString, props); |
|
Statement stmt = conn.createStatement(); |
|
) { |
|
for (String dbName : TEST_DBS) { |
|
String databaseName = getTestDbName(dbName); |
|
String dropSql = String.format("DROP DATABASE IF EXISTS %s;", databaseName); |
|
stmt.execute(dropSql); |
|
} |
|
} catch (SQLException e) { |
|
System.out.println("Problem bei Bereinigung der Testdatenbank:"); |
|
e.printStackTrace(); |
|
} |
|
} |
|
|
|
protected void ensureLatestQisdevDumps(String dateStr) throws SQLException { |
|
//System.out.println("entering ensureLatestQisdevDumps()"); |
|
/* Warum werden hier 19:30 abgezogen? |
|
* Die Klasse spielt Dumps ein und macht daraus template-Datenbanken, |
|
* aus welchen dann Testdatenbanken erzeugt werden. |
|
* Das sollte möglichst während der nächtlichen Tests passieren, weil es lange dauert, |
|
* beim Entwickeln will man nicht, dass sowas passiert. |
|
* Um nicht auf veralteten Daten zu arbeiten, kommt ein Timestamp des Erzeugungszeitpunktes |
|
* in den Templatenamen - Bsp. "2025_09_04". |
|
* Man weiss aber nicht sicher, ob das Template während des Builds vor oder nach Mitternacht |
|
* erzeugt wird. Wenn das vor Mitternacht passiert und dann nach Mitternacht plötzlich den |
|
* Timestamp des Vortages hat, würde es bereits als veraltet gelten und bei der ersten |
|
* Ausführung eines Testfalls müsste man auf den Neuaufbau warten. |
|
* Daher die Lösung: |
|
* Man zieht bei der Erzeugung des Timestampt noch 19:30 ab; so wechselt der Timestamp erst |
|
* nach Feierabend auf den nächsten Tag. |
|
* */ |
|
if( !templateExists(EDUETL_DB, branchName, dateStr)) { |
|
System.out.println(String.format("kein %s template vorhanden...erstelle", EDUETL_DB)); |
|
restoreDump(EDUETL_DB, branchName, dateStr); |
|
createHis1UnitTestDb(EDUETL_DB, branchName, dateStr); |
|
} else { |
|
System.out.println(String.format("%s template bereits vorhanden...", EDUETL_DB)); |
|
} |
|
if( !templateExists(HISINONE_DB, branchName, dateStr)) { |
|
System.out.println(String.format("kein %s template vorhanden...erstelle", HISINONE_DB)); |
|
restoreDump(HISINONE_DB, branchName, dateStr); |
|
createHis1UnitTestDb(HISINONE_DB, branchName, dateStr); |
|
} else { |
|
System.out.println(String.format("%s template bereits vorhanden...", HISINONE_DB)); |
|
} |
|
} |
|
|
|
protected String getTestDbName(String name) { |
|
if ( !this.testDbNames.containsKey(name) ) { |
|
SimpleDateFormat dateformat = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss"); |
|
String timestamp = dateformat.format(new Date()); |
|
try { |
|
this.testDbNames.put(name, String.format(PREFIX + "%s_%s_%s_%s", |
|
name, |
|
InetAddress.getLocalHost().getHostName().replace("-", "_"), |
|
branchName, |
|
timestamp)); |
|
} catch (UnknownHostException e) { |
|
e.printStackTrace(); |
|
} |
|
} |
|
return this.testDbNames.get(name); |
|
} |
|
|
|
protected String getJdbcUrl(String name) { |
|
return CONNECTION_STRING_PREFIX + getTestDbName(name); |
|
} |
|
|
|
protected static Properties dbConnectionProperties() { |
|
//System.out.println("entering dbConnectionProperties()"); |
|
Properties props = new Properties(); |
|
props.setProperty("user", TestDbManagerPg.USER); |
|
props.setProperty("password", TestDbManagerPg.PASSWORD); |
|
props.setProperty("ssl", "false"); |
|
return props; |
|
} |
|
|
|
private String getBranchName() { |
|
if ( this.branchName == null ) { |
|
String[] branch_name_command = {"git", "branch", "--show-current"}; |
|
ProcessBuilder branch_name = new ProcessBuilder(branch_name_command); |
|
try { |
|
Process branch_name_proc; |
|
branch_name_proc = branch_name.start(); |
|
try (InputStreamReader branch_name_proc_stderr = new InputStreamReader(branch_name_proc.getErrorStream()); |
|
InputStreamReader branch_name_proc_stdout = new InputStreamReader(branch_name_proc.getInputStream())) { |
|
String err0 = new BufferedReader(branch_name_proc_stderr).lines().collect(Collectors.joining("\n")); |
|
String output = new BufferedReader(branch_name_proc_stdout).lines().collect(Collectors.joining("\n")); |
|
String branchNameFound = output.trim().toLowerCase(); |
|
Matcher matcher = BRANCH_NAME_PATTERN.matcher(branchNameFound); |
|
if ( matcher.find() ) { |
|
System.out.println(String.format("git branch '%s' für Auswahl der Test DB gefunden", branchNameFound)); |
|
this.branchName = branchNameFound; |
|
} else { |
|
System.out.println("konnte keinen zu db dumps passenden git branch finden - benutze 'master'"); |
|
this.branchName = "master"; |
|
} |
|
} |
|
} catch (IOException e) { |
|
// TODO Auto-generated catch block |
|
e.printStackTrace(); |
|
} |
|
} |
|
return this.branchName; |
|
} |
|
|
|
private static boolean templateExists(String database, String branch, String dateStr) throws SQLException { |
|
//System.out.println("entering templateExists()"); |
|
String templateName = getTemplateName(database, branch, dateStr); |
|
String url = CONNECTION_STRING; |
|
Properties props = dbConnectionProperties(); |
|
try (Connection conn = DriverManager.getConnection(url, props); Statement stmt = conn.createStatement();) { |
|
String sql = String.format("SELECT datname FROM pg_database WHERE datname = '%s';", templateName); |
|
ResultSet result = stmt.executeQuery(sql); |
|
return result.next(); |
|
} catch (SQLException e) { |
|
System.out.println("Konnte die Buildnummer des Templates von '" + database + "' nicht prüfen:"); |
|
e.printStackTrace(); |
|
throw e; |
|
} |
|
} |
|
|
|
private static String getTemplateName(String database, String branch, String dateStr) { |
|
return String.format(PREFIX + "template_%s_%s_%s", database, branch, dateStr); |
|
} |
|
|
|
private void restoreDump(String database, String branch, String dateStr) { |
|
//System.out.println("entering restoreDump()"); |
|
String templateName = getTemplateName(database, branch, dateStr); |
|
String connectionString = CONNECTION_STRING; |
|
Properties props = dbConnectionProperties(); |
|
try ( |
|
Connection conn = DriverManager.getConnection(connectionString, props); |
|
Statement createStmt = conn.createStatement(); |
|
) { |
|
String createSql = String.format("CREATE DATABASE %s;", templateName); |
|
createStmt.execute(createSql); |
|
String url = String.format(branch.equals("master") ? QISDEV_HIS1_DUMP_URL : QISDEV_HIS1_DUMP_URL_BRANCH, branch.equals("master") ? "HEAD" : branch.toUpperCase()); |
|
if ("eduetl".equals(database)) { |
|
url = String.format(QISDEV_EDUETL_DUMP_URL, "master".equals(branchName) ? "HEAD" : branchName.toUpperCase()); |
|
} |
|
String[] dump_dl_command = {"curl", "-o", database + ".backup", url}; |
|
ProcessBuilder dump_download = new ProcessBuilder(dump_dl_command); |
|
String[] dump_restore_command = {"pg_restore", "-h", SERVER, "-U", USER, "-d", templateName, database + ".backup"}; |
|
ProcessBuilder dump_restore = new ProcessBuilder(dump_restore_command); |
|
dump_restore.environment().put("PGPASSWORD", PASSWORD); |
|
Process dl_proc = dump_download.start(); |
|
try (InputStreamReader dl_proc_stdin = new InputStreamReader(dl_proc.getInputStream()); |
|
InputStreamReader dl_proc_stderr = new InputStreamReader(dl_proc.getErrorStream())) { |
|
String output = new BufferedReader(dl_proc_stdin).lines().collect(Collectors.joining("\n")); |
|
String err0 = new BufferedReader(dl_proc_stderr).lines().collect(Collectors.joining("\n")); |
|
System.out.println(output); |
|
System.out.println(err0); |
|
} |
|
Process rest_proc = dump_restore.start(); |
|
try (InputStreamReader rest_proc_stderr = new InputStreamReader(rest_proc.getErrorStream())) { |
|
String err1 = new BufferedReader(rest_proc_stderr).lines().collect(Collectors.joining("\n")); |
|
System.out.println(err1); |
|
} |
|
String[] dump_rm_command = {"rm", database + ".backup"}; |
|
ProcessBuilder dump_remove = new ProcessBuilder(dump_rm_command); |
|
dump_remove.start(); |
|
} catch (SQLException e) { |
|
System.out.println("Schade, etwas ging bei der Datenbankanlage schief:"); |
|
e.printStackTrace(); |
|
} catch (IOException e) { |
|
System.out.println("Schade, etwas ging beim Datenbankeinspielen schief:"); |
|
e.printStackTrace(); |
|
} |
|
cleanupObsoleteTemplates(database, branch, dateStr); |
|
cleanupOldUserDbs(database); |
|
} |
|
|
|
private static void createHis1UnitTestDb(String database, String branch, String dateStr) { |
|
String connectionString = CONNECTION_STRING; |
|
Properties props = dbConnectionProperties(); |
|
try ( |
|
Connection conn = DriverManager.getConnection(connectionString, props); |
|
Statement stmt = conn.createStatement(); |
|
) { |
|
String templateName = getTemplateName(database, branch, dateStr); |
|
String databaseName = "bi_" + database + "_" + branch; |
|
System.out.println(String.format("Erstelle %s ", databaseName)); |
|
String createSql = String.format("DROP DATABASE IF EXISTS %s; CREATE DATABASE %s TEMPLATE %s;", databaseName, databaseName, templateName); |
|
stmt.execute(createSql); |
|
} catch (SQLException e) { |
|
System.out.println("Schade, etwas ging bei der Datenbankanlage schief:"); |
|
e.printStackTrace(); |
|
} |
|
} |
|
|
|
private static void cleanupObsoleteTemplates(String database, String branch, String dateStr) { |
|
String connectionString = CONNECTION_STRING; |
|
Properties props = dbConnectionProperties(); |
|
try ( |
|
Connection conn = DriverManager.getConnection(connectionString, props); |
|
Statement lookupStmt = conn.createStatement(); |
|
Statement deleteStmt = conn.createStatement(); |
|
) { |
|
String lookupSql = String.format( |
|
"SELECT datname FROM pg_database WHERE datname LIKE '" + PREFIX + "template_%s_%s%%' AND NOT datname = '" + PREFIX + "template_%s_%s_%s'", |
|
database, branch, database, branch, dateStr ); |
|
ResultSet result = lookupStmt.executeQuery(lookupSql); |
|
StringBuilder sb = new StringBuilder(); |
|
while(result.next()) { |
|
sb.append(String.format("DROP DATABASE %s;", result.getString("datname"))); |
|
} |
|
String dropSql = sb.toString(); |
|
deleteStmt.execute(dropSql); |
|
} catch (SQLException e) { |
|
System.out.println("Schade, etwas ging bei der Datenbankanlage schief:"); |
|
e.printStackTrace(); |
|
} |
|
} |
|
|
|
private static void cleanupOldUserDbs(String database) { |
|
String connectionString = CONNECTION_STRING; |
|
Properties props = dbConnectionProperties(); |
|
try ( |
|
Connection conn = DriverManager.getConnection(connectionString, props); |
|
Statement lookupStmt = conn.createStatement(); |
|
Statement deleteStmt = conn.createStatement(); |
|
) { |
|
String lookupSql = String.format( |
|
"SELECT datname FROM pg_database WHERE datname LIKE '" + PREFIX + "%s_%%'", |
|
database); |
|
ResultSet result = lookupStmt.executeQuery(lookupSql); |
|
StringBuilder sb = new StringBuilder(); |
|
while(result.next()) { |
|
String dbName = result.getString("datname"); |
|
String[] nameParts = dbName.split("_"); |
|
String year = nameParts[nameParts.length - 6]; |
|
String month = nameParts[nameParts.length - 5]; |
|
String day = nameParts[nameParts.length - 4]; |
|
LocalDateTime today = LocalDateTime.now(); |
|
LocalDateTime then = LocalDate.parse(year + month + day, DateTimeFormatter.BASIC_ISO_DATE).atStartOfDay(); |
|
if (today.isAfter(then)) { |
|
sb.append(String.format("DROP DATABASE %s;", dbName)); |
|
} |
|
} |
|
String dropSql = sb.toString(); |
|
deleteStmt.execute(dropSql); |
|
} catch (SQLException e) { |
|
System.out.println("Schade, etwas ging bei der Bereinigung der alten Benutzer-Datenbanken schief:"); |
|
e.printStackTrace(); |
|
} |
|
} |
|
|
|
@Override |
|
public Map<String, String> dbtProfile() { |
|
Map<String, String> attributes = new HashMap<>(); |
|
attributes.put("DBT_HOST", SERVER); |
|
attributes.put("DBT_PORT", "5432"); |
|
attributes.put("DBT_USER", USER); |
|
attributes.put("DBT_ENV_SECRET_PW", PASSWORD); |
|
attributes.put("DBT_DB", getTestDbName(EDUETL_DB)); |
|
return attributes; |
|
} |
|
|
|
} |