diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 68799c4907c703ebfa80805cad5108faf5a184af..f9692a38cb1702a61ab6599b8c62640fa41a62c8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -158,6 +158,7 @@ dbunittests_postgresql: - echo 'postgresql:postgresql://cta@localhost/cta' > ${CTA_CATALOGUE_CONF} - /usr/bin/cta-catalogue-schema-create ${CTA_CATALOGUE_CONF} - /usr/bin/cta-rdbmsUnitTests ${CTA_CATALOGUE_CONF} + - valgrind /usr/bin/cta-rdbmsUnitTests ${CTA_CATALOGUE_CONF} - runuser -u postgres -- ${POSTGRES_BIN}/pg_ctl stop -D ${POSTGRESQL_DATA_DIR} tags: @@ -192,6 +193,7 @@ dbunittests_mysql: - echo 'mysql://cta@localhost:3306/cta' > ${CTA_CATALOGUE_CONF} - /usr/bin/cta-catalogue-schema-create ${CTA_CATALOGUE_CONF} - /usr/bin/cta-rdbmsUnitTests ${CTA_CATALOGUE_CONF} + - valgrind /usr/bin/cta-rdbmsUnitTests ${CTA_CATALOGUE_CONF} - kill `ps -ef | egrep '^mysql.*/usr/libexec/mysqld' | awk '{print $2;}'` tags: diff --git a/catalogue/Catalogue.hpp b/catalogue/Catalogue.hpp index 5424fa3d859879221e0d7a052d2ff07db9ad1cf6..8c781cab51e1a59ae95a8bf16f32223f97f686ac 100644 --- a/catalogue/Catalogue.hpp +++ b/catalogue/Catalogue.hpp @@ -246,6 +246,16 @@ public: virtual void modifyStorageClassNbCopies(const common::dataStructures::SecurityIdentity &admin, const std::string &instanceName, const std::string &name, const uint64_t nbCopies) = 0; virtual void modifyStorageClassComment(const common::dataStructures::SecurityIdentity &admin, const std::string &instanceName, const std::string &name, const std::string &comment) = 0; + /** + * Modifies the name of the specified storage class. + * + * @param diskInstanceName The name of the disk instance to which the + * storage class belongs. + * @param currentName The current name of the storage class. + * @param newName The new name of the storage class. + */ + virtual void modifyStorageClassName(const common::dataStructures::SecurityIdentity &admin, const std::string &instanceName, const std::string ¤tName, const std::string &newName) = 0; + virtual void createTapePool(const common::dataStructures::SecurityIdentity &admin, const std::string &name, const std::string &vo, const uint64_t nbPartialTapes, const bool encryptionValue, const cta::optional<std::string> &supply, const std::string &comment) = 0; virtual void deleteTapePool(const std::string &name) = 0; virtual std::list<TapePool> getTapePools() const = 0; diff --git a/catalogue/CatalogueRetryWrapper.hpp b/catalogue/CatalogueRetryWrapper.hpp index 73797d6a3fe197ee565c89febd4d92b3217b45cc..3231bb28fa11a77422a9c0cbf068b995b0f5c493 100644 --- a/catalogue/CatalogueRetryWrapper.hpp +++ b/catalogue/CatalogueRetryWrapper.hpp @@ -137,6 +137,10 @@ public: return retryOnLostConnection(m_log, [&]{return m_catalogue->modifyStorageClassComment(admin, instanceName, name, comment);}, m_maxTriesToConnect); } + void modifyStorageClassName(const common::dataStructures::SecurityIdentity &admin, const std::string &instanceName, const std::string ¤tName, const std::string &newName) override { + return retryOnLostConnection(m_log, [&]{return m_catalogue->modifyStorageClassName(admin, instanceName, currentName, newName);}, m_maxTriesToConnect); + } + void createTapePool(const common::dataStructures::SecurityIdentity &admin, const std::string &name, const std::string &vo, const uint64_t nbPartialTapes, const bool encryptionValue, const cta::optional<std::string> &supply, const std::string &comment) override { return retryOnLostConnection(m_log, [&]{return m_catalogue->createTapePool(admin, name, vo, nbPartialTapes, encryptionValue, supply, comment);}, m_maxTriesToConnect); } diff --git a/catalogue/CatalogueTest.cpp b/catalogue/CatalogueTest.cpp index 106b23ad10f916372890284c312909fc0d350e59..ae8120016177646ed65695cba907be43e5e36ef2 100644 --- a/catalogue/CatalogueTest.cpp +++ b/catalogue/CatalogueTest.cpp @@ -809,6 +809,67 @@ TEST_P(cta_catalogue_CatalogueTest, modifyStorageClassComment_nonExistentStorage exception::UserError); } +TEST_P(cta_catalogue_CatalogueTest, modifyStorageClassName) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getStorageClasses().empty()); + + common::dataStructures::StorageClass storageClass; + storageClass.diskInstance = "disk_instance"; + storageClass.name = "storage_class"; + storageClass.nbCopies = 2; + storageClass.comment = "Create storage class"; + m_catalogue->createStorageClass(m_admin, storageClass); + + { + const std::list<common::dataStructures::StorageClass> storageClasses = m_catalogue->getStorageClasses(); + + ASSERT_EQ(1, storageClasses.size()); + + ASSERT_EQ(storageClass.diskInstance, storageClasses.front().diskInstance); + ASSERT_EQ(storageClass.name, storageClasses.front().name); + ASSERT_EQ(storageClass.nbCopies, storageClasses.front().nbCopies); + ASSERT_EQ(storageClass.comment, storageClasses.front().comment); + + const common::dataStructures::EntryLog creationLog = storageClasses.front().creationLog; + ASSERT_EQ(m_admin.username, creationLog.username); + ASSERT_EQ(m_admin.host, creationLog.host); + + const common::dataStructures::EntryLog lastModificationLog = storageClasses.front().lastModificationLog; + ASSERT_EQ(creationLog, lastModificationLog); + } + + const std::string newStorageClassName = "new_storage_class_name"; + m_catalogue->modifyStorageClassName(m_admin, storageClass.diskInstance, storageClass.name, newStorageClassName); + + { + const std::list<common::dataStructures::StorageClass> storageClasses = m_catalogue->getStorageClasses(); + + ASSERT_EQ(1, storageClasses.size()); + + ASSERT_EQ(storageClass.diskInstance, storageClasses.front().diskInstance); + ASSERT_EQ(newStorageClassName, storageClasses.front().name); + ASSERT_EQ(storageClass.nbCopies, storageClasses.front().nbCopies); + ASSERT_EQ(storageClass.comment, storageClasses.front().comment); + + const common::dataStructures::EntryLog creationLog = storageClasses.front().creationLog; + ASSERT_EQ(m_admin.username, creationLog.username); + ASSERT_EQ(m_admin.host, creationLog.host); + } +} + +TEST_P(cta_catalogue_CatalogueTest, modifyStorageClassName_nonExistentStorageClass) { + using namespace cta; + + ASSERT_TRUE(m_catalogue->getStorageClasses().empty()); + + const std::string diskInstance = "disk_instance"; + const std::string currentStorageClassName = "storage_class"; + const std::string newStorageClassName = "new_storage_class"; + ASSERT_THROW(m_catalogue->modifyStorageClassName(m_admin, diskInstance, currentStorageClassName, newStorageClassName), + exception::UserError); +} + TEST_P(cta_catalogue_CatalogueTest, createTapePool) { using namespace cta; diff --git a/catalogue/DummyCatalogue.hpp b/catalogue/DummyCatalogue.hpp index 4faf50c85f9a151d491128c8dfa6083006659c88..9a56ddb476f3d7996d73fcb9521e5dcffa384fe8 100644 --- a/catalogue/DummyCatalogue.hpp +++ b/catalogue/DummyCatalogue.hpp @@ -101,6 +101,7 @@ public: void modifyRequesterGroupMountRulePolicy(const common::dataStructures::SecurityIdentity& admin, const std::string& instanceName, const std::string& requesterGroupName, const std::string& mountPolicy) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } void modifyRequesterMountRulePolicy(const common::dataStructures::SecurityIdentity& admin, const std::string& instanceName, const std::string& requesterName, const std::string& mountPolicy) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } void modifyStorageClassComment(const common::dataStructures::SecurityIdentity& admin, const std::string& instanceName, const std::string& name, const std::string& comment) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } + void modifyStorageClassName(const common::dataStructures::SecurityIdentity& admin, const std::string& instanceName, const std::string& currentName, const std::string& newName) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } void modifyStorageClassNbCopies(const common::dataStructures::SecurityIdentity& admin, const std::string& instanceName, const std::string& name, const uint64_t nbCopies) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } void modifyTapeCapacityInBytes(const common::dataStructures::SecurityIdentity& admin, const std::string& vid, const uint64_t capacityInBytes) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } void modifyTapeComment(const common::dataStructures::SecurityIdentity& admin, const std::string& vid, const std::string& comment) override { throw exception::Exception(std::string("In ")+__PRETTY_FUNCTION__+": not implemented"); } diff --git a/catalogue/RdbmsCatalogue.cpp b/catalogue/RdbmsCatalogue.cpp index ca64d317fcc403da9b999435abb4b08dfcf6562e..bac4602501426a44ae0ce1456adf6913c6c660c3 100644 --- a/catalogue/RdbmsCatalogue.cpp +++ b/catalogue/RdbmsCatalogue.cpp @@ -639,6 +639,44 @@ void RdbmsCatalogue::modifyStorageClassComment(const common::dataStructures::Sec } } +//------------------------------------------------------------------------------ +// modifyStorageClassName +//------------------------------------------------------------------------------ +void RdbmsCatalogue::modifyStorageClassName(const common::dataStructures::SecurityIdentity &admin, + const std::string &instanceName, const std::string ¤tName, const std::string &newName) { + try { + const time_t now = time(nullptr); + const char *const sql = + "UPDATE STORAGE_CLASS SET " + "STORAGE_CLASS_NAME = :NEW_STORAGE_CLASS_NAME," + "LAST_UPDATE_USER_NAME = :LAST_UPDATE_USER_NAME," + "LAST_UPDATE_HOST_NAME = :LAST_UPDATE_HOST_NAME," + "LAST_UPDATE_TIME = :LAST_UPDATE_TIME " + "WHERE " + "DISK_INSTANCE_NAME = :DISK_INSTANCE_NAME AND " + "STORAGE_CLASS_NAME = :CURRENT_STORAGE_CLASS_NAME"; + auto conn = m_connPool.getConn(); + auto stmt = conn.createStmt(sql); + stmt.bindString(":NEW_STORAGE_CLASS_NAME", newName); + stmt.bindString(":LAST_UPDATE_USER_NAME", admin.username); + stmt.bindString(":LAST_UPDATE_HOST_NAME", admin.host); + stmt.bindUint64(":LAST_UPDATE_TIME", now); + stmt.bindString(":DISK_INSTANCE_NAME", instanceName); + stmt.bindString(":CURRENT_STORAGE_CLASS_NAME", currentName); + stmt.executeNonQuery(); + + if(0 == stmt.getNbAffectedRows()) { + throw exception::UserError(std::string("Cannot modify storage class ") + instanceName + ":" + currentName + + " because it does not exist"); + } + } catch(exception::UserError &) { + throw; + } catch(exception::Exception &ex) { + ex.getMessage().str(std::string(__FUNCTION__) + ": " + ex.getMessage().str()); + throw; + } +} + //------------------------------------------------------------------------------ // createTapePool //------------------------------------------------------------------------------ diff --git a/catalogue/RdbmsCatalogue.hpp b/catalogue/RdbmsCatalogue.hpp index 8de37183d37975c593b9b26a5a86e9daaad5ebdb..8fdda7a41f5f1ec3ef0f903f62c65afde5aba884 100644 --- a/catalogue/RdbmsCatalogue.hpp +++ b/catalogue/RdbmsCatalogue.hpp @@ -236,6 +236,16 @@ public: void modifyStorageClassNbCopies(const common::dataStructures::SecurityIdentity &admin, const std::string &instanceName, const std::string &name, const uint64_t nbCopies) override; void modifyStorageClassComment(const common::dataStructures::SecurityIdentity &admin, const std::string &instanceName, const std::string &name, const std::string &comment) override; + /** + * Modifies the name of the specified storage class. + * + * @param diskInstanceName The name of the disk instance to which the + * storage class belongs. + * @param currentName The current name of the storage class. + * @param newName The new name of the storage class. + */ + void modifyStorageClassName(const common::dataStructures::SecurityIdentity &admin, const std::string &instanceName, const std::string ¤tName, const std::string &newName) override; + void createTapePool(const common::dataStructures::SecurityIdentity &admin, const std::string &name, const std::string &vo, const uint64_t nbPartialTapes, const bool encryptionValue, const cta::optional<std::string> &supply, const std::string &comment) override; void deleteTapePool(const std::string &name) override; std::list<TapePool> getTapePools() const override; diff --git a/continuousintegration/docker/ctafrontend/cc7/opt/run/bin/oracleunittests.sh b/continuousintegration/docker/ctafrontend/cc7/opt/run/bin/oracleunittests.sh new file mode 100755 index 0000000000000000000000000000000000000000..36641b00a687e31fa5a63bc734abec828632a4a2 --- /dev/null +++ b/continuousintegration/docker/ctafrontend/cc7/opt/run/bin/oracleunittests.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +set -e + +. /opt/run/bin/init_pod.sh + +if [ ! -e /etc/buildtreeRunner ]; then + yum-config-manager --enable cta-artifacts + yum-config-manager --enable ceph + yum-config-manager --enable castor + + # Install missing RPMs + yum -y install mt-st lsscsi sg3_utils cta-taped cta-tape-label cta-debuginfo ceph-common +fi + +echo "Configuring database" +/opt/run/bin/init_database.sh +. /tmp/database-rc.sh + +echo ${DATABASEURL} > /etc/cta/cta-catalogue.conf + +yum -y install cta-catalogueutils cta-systemtests + +/usr/bin/cta-rdbmsUnitTests /etc/cta/cta-catalogue.conf + +echo 'yes' | /usr/bin/cta-catalogue-schema-drop /etc/cta/cta-catalogue.conf +/usr/bin/cta-catalogue-schema-create /etc/cta/cta-catalogue.conf diff --git a/continuousintegration/orchestration/create_instance.sh b/continuousintegration/orchestration/create_instance.sh index 57825ecf9ce858c0ca50b1d96ed7b80497ed8d58..493e33368433a1691116d0209c0d83820306d670 100755 --- a/continuousintegration/orchestration/create_instance.sh +++ b/continuousintegration/orchestration/create_instance.sh @@ -255,6 +255,28 @@ fi kubectl get pod init -a --namespace=${instance} | grep -q Completed || die "TIMED OUT" echo OK +echo "Running database unit-tests" +kubectl create -f ${poddir}/pod-oracleunittests.yaml --namespace=${instance} + +echo -n "Waiting for oracleunittests" +for ((i=0; i<400; i++)); do + echo -n "." + kubectl get pod oracleunittests -a --namespace=${instance} | egrep -q 'Completed|Error' && break + sleep 1 +done + +kubectl --namespace=${instance} logs oracleunittests + +# database unit-tests went wrong => exit now with error +if $(kubectl get pod oracleunittests -a --namespace=${instance} | grep -q Error); then + echo "init pod in Error status here are its last log lines:" + kubectl --namespace=${instance} logs oracleunittests --tail 10 + die "ERROR: init pod in ErERROR: oracleunittests pod in Error state. Initialization failed." +fi + +kubectl get pod init -a --namespace=${instance} | grep -q Completed || die "TIMED OUT" +echo OK + echo "Launching pods" for podname in client ctacli tpsrv01 tpsrv02 ctaeos ctafrontend kdc; do diff --git a/continuousintegration/orchestration/pod-oracleunittests.yaml b/continuousintegration/orchestration/pod-oracleunittests.yaml new file mode 100644 index 0000000000000000000000000000000000000000..255aae9ac2ea75ac0bd187742f4fb9c45d123a96 --- /dev/null +++ b/continuousintegration/orchestration/pod-oracleunittests.yaml @@ -0,0 +1,77 @@ +apiVersion: v1 +kind: Pod +metadata: + name: oracleunittests +spec: + restartPolicy: Never + containers: + - name: oracleunittests + image: gitlab-registry.cern.ch/cta/ctageneric:78673git921a9300 + stdin: true + env: + - name: MY_CONTAINER + value: "oracleunittests" + - name: MY_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: MY_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: INSTANCE_NAME + value: "$(MY_NAMESPACE)" + - name: BUILDTREE_BASE + valueFrom: + configMapKeyRef: + name: buildtree + key: base + - name: CTA_BUILDTREE_SUBDIR + valueFrom: + configMapKeyRef: + name: buildtree + key: cta_subdir + - name: EOS_BUILDTREE_SUBDIR + valueFrom: + configMapKeyRef: + name: buildtree + key: eos_subdir + - name: TERM + value: "xterm" + - name: driveslot + value: "0" + command: ['/opt/run/bin/oracleunittests.sh'] + args: ["none"] + volumeMounts: + - mountPath: /shared + name: shared + - mountPath: /etc/config/objectstore + name: myobjectstore + - mountPath: /etc/config/database + name: mydatabase + - mountPath: /etc/config/library + name: mylibrary + - mountPath: /mnt/logs + name: logstorage + securityContext: + privileged: true + + volumes: + - name: shared + hostPath: + path: /opt/cta + - name: myobjectstore + configMap: + name: objectstore-config + - name: mydatabase + configMap: + name: database-config + - name: mylibrary + configMap: + name: library-config + - name: logstorage + persistentVolumeClaim: + claimName: claimlogs + + imagePullSecrets: + - name: ctaregsecret diff --git a/rdbms/StmtTest.cpp b/rdbms/StmtTest.cpp index 4aef36d0ce6160bcb4b3092a92329eff19c06c95..c48a794dbf3c1be48978f6624e266289f8b1ed23 100644 --- a/rdbms/StmtTest.cpp +++ b/rdbms/StmtTest.cpp @@ -231,6 +231,44 @@ TEST_P(cta_rdbms_StmtTest, insert_with_bindUint64_2_pow_64_minus_1) { } } +TEST_P(cta_rdbms_StmtTest, insert_with_bindUint64_2_pow_64_minus_2) { + using namespace cta::rdbms; + + const uint64_t insertValue = 18446744073709551614U; + + // Insert a row into the test table + { + const char *const sql = + "INSERT INTO STMT_TEST(" + "UINT64_COL) " + "VALUES(" + ":UINT64_COL)"; + auto stmt = m_conn.createStmt(sql); + stmt.bindUint64(":UINT64_COL", insertValue); + stmt.executeNonQuery(); + } + + // Select the row back from the table + { + const char *const sql = + "SELECT " + "UINT64_COL AS UINT64_COL " + "FROM " + "STMT_TEST"; + auto stmt = m_conn.createStmt(sql); + auto rset = stmt.executeQuery(); + ASSERT_TRUE(rset.next()); + + const auto selectValue = rset.columnOptionalUint64("UINT64_COL"); + + ASSERT_TRUE((bool)selectValue); + + ASSERT_EQ(insertValue,selectValue.value()); + + ASSERT_FALSE(rset.next()); + } +} + TEST_P(cta_rdbms_StmtTest, insert_with_bindString) { using namespace cta::rdbms; diff --git a/rdbms/wrapper/MysqlStmt.cpp b/rdbms/wrapper/MysqlStmt.cpp index 8279bae172b083fdb61d9f30d0af41547ccdd4b0..31c713947f302d15762664792cc3199e2df0ab7f 100644 --- a/rdbms/wrapper/MysqlStmt.cpp +++ b/rdbms/wrapper/MysqlStmt.cpp @@ -189,14 +189,14 @@ void MysqlStmt::bindOptionalUint64(const std::string ¶mName, const optional< const unsigned int paramIdx = getParamIdx(paramName); // starts from 1. const unsigned int idx = paramIdx - 1; - Mysql::Placeholder_Double* holder = dynamic_cast<Mysql::Placeholder_Double*>(m_placeholder[idx]); + Mysql::Placeholder_Uint64* holder = dynamic_cast<Mysql::Placeholder_Uint64*>(m_placeholder[idx]); // if already exists, try to reuse if (!holder and m_placeholder[idx]) { throw exception::Exception(std::string(__FUNCTION__) + " can't cast from Placeholder to Placeholder_Uint64. " ); } if (!holder) { - holder = new Mysql::Placeholder_Double(); + holder = new Mysql::Placeholder_Uint64(); holder->idx = idx; holder->length = sizeof(uint64_t); } @@ -316,7 +316,7 @@ void MysqlStmt::bindOptionalString(const std::string ¶mName, const optional< // reset memory holder->reset(); // need to use memcpy for VARBINARY strings, which are not null-terminated - memcpy(holder->val, paramValue.value().c_str(), holder->get_buffer_length()); + memcpy(holder->val, paramValue.value().c_str(), paramValue.value().size()); } else { holder->length = 0; }