From cfd621e9d000393b53f19724d7ce8f788f04663e Mon Sep 17 00:00:00 2001
From: Eric Cano <Eric.Cano@cern.ch>
Date: Tue, 10 Sep 2019 16:07:45 +0200
Subject: [PATCH] #252: Created and packaged a C++ based utility as an
 atlernative to the python based helper.

---
 CMakeLists.txt                                |   2 +
 .../ctafrontend/cc7/opt/run/bin/client.sh     |   2 +-
 .../orchestration/tests/CMakeLists.txt        |  26 +++
 .../tests/client-ar-abortPrepare.cpp          | 161 ++++++++++++++++++
 .../orchestration/tests/client_ar.sh          |  10 +-
 cta.spec.in                                   |  10 ++
 6 files changed, 206 insertions(+), 5 deletions(-)
 create mode 100644 continuousintegration/orchestration/tests/CMakeLists.txt
 create mode 100644 continuousintegration/orchestration/tests/client-ar-abortPrepare.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 77f6db411a..92b3c3be1a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -152,6 +152,8 @@ ELSE(DEFINED PackageOnly)
   add_subdirectory(tapeserver)
   add_subdirectory(XRootdSSiRmcd)
 
+  add_subdirectory(continuousintegration/orchestration/tests)
+
   #Generate version information
   configure_file(${PROJECT_SOURCE_DIR}/version.hpp.in
     ${CMAKE_BINARY_DIR}/version.h)
diff --git a/continuousintegration/docker/ctafrontend/cc7/opt/run/bin/client.sh b/continuousintegration/docker/ctafrontend/cc7/opt/run/bin/client.sh
index 21ad0cf692..627afc14bd 100755
--- a/continuousintegration/docker/ctafrontend/cc7/opt/run/bin/client.sh
+++ b/continuousintegration/docker/ctafrontend/cc7/opt/run/bin/client.sh
@@ -7,7 +7,7 @@ if [ ! -e /etc/buildtreeRunner ]; then
   yum-config-manager --enable ceph
 
   # Install missing RPMs
-  yum -y install cta-cli cta-debuginfo xrootd-client eos-client jq python36
+  yum -y install cta-cli cta-systemtest-helpers cta-debuginfo xrootd-client eos-client jq python36
 
   ## Keep this temporary fix that may be needed if going to protobuf3-3.5.1 for CTA
   # Install eos-protobuf3 separately as eos is OK with protobuf3 but cannot use it..
diff --git a/continuousintegration/orchestration/tests/CMakeLists.txt b/continuousintegration/orchestration/tests/CMakeLists.txt
new file mode 100644
index 0000000000..53e8a9fa51
--- /dev/null
+++ b/continuousintegration/orchestration/tests/CMakeLists.txt
@@ -0,0 +1,26 @@
+# The CERN Tape Archive(CTA) project
+# Copyright(C) 2015  CERN
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+cmake_minimum_required (VERSION 2.6)
+
+find_package (xrootd REQUIRED)
+find_package (xrootdclient REQUIRED)
+
+include_directories (${XROOTD_INCLUDE_DIR} ${CMAKE_SOURCE_DIR})
+
+add_executable(cta-client-ar-abortPrepare client-ar-abortPrepare.cpp)
+target_link_libraries(cta-client-ar-abortPrepare XrdCl ctacommon)
+install(TARGETS cta-client-ar-abortPrepare DESTINATION usr/bin)
diff --git a/continuousintegration/orchestration/tests/client-ar-abortPrepare.cpp b/continuousintegration/orchestration/tests/client-ar-abortPrepare.cpp
new file mode 100644
index 0000000000..6e242cacfb
--- /dev/null
+++ b/continuousintegration/orchestration/tests/client-ar-abortPrepare.cpp
@@ -0,0 +1,161 @@
+/*
+ * The CERN Tape Archive (CTA) project
+ * Copyright (C) 2015  CERN
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <getopt.h>
+#include <string>
+#include <iostream>
+#include <memory>
+#include <XrdCl/XrdClFileSystem.hh>
+#include "common/utils/Regex.hpp"
+#include "common/exception/XrootCl.hpp"
+
+
+// No short options.
+const char short_options[] = "";  
+// We require a handful of long options.
+
+enum class OptionIds: int {
+  eos_instance = 1,
+  eos_poweruser = 2,
+  eos_dir = 3,
+  subdir = 4,
+  file = 5,
+  error_dir = 6
+};
+
+const struct ::option long_options[] = {
+  { "eos-instance", required_argument, nullptr, (int)OptionIds::eos_instance },
+  { "eos-poweruser", required_argument, nullptr, (int)OptionIds::eos_poweruser },
+  { "eos-dir", required_argument, nullptr, (int)OptionIds::eos_dir },
+  { "subdir", required_argument, nullptr, (int)OptionIds::subdir },
+  { "file", required_argument, nullptr, (int)OptionIds::file },
+  { "error-dir", required_argument, nullptr, (int)OptionIds::error_dir },
+  { nullptr, 0, nullptr, 0 }
+};
+
+void help() {
+  std::cerr << "Expected parameters are: ";
+  const struct ::option * pOpt = long_options;
+  while (pOpt->name) {
+    std::cerr << "--" << pOpt->name << " ";
+    ++pOpt;
+  }
+  std::cerr << std::endl;
+}
+
+// We make these variables global as they will be part of the process's environment.
+char envXRD_LOGLEVEL[] = "XRD_LOGLEVEL=Dump";
+std::unique_ptr<char[]> envKRB5CCNAME;
+char envXrdSecPROTOCOL[] = "XrdSecPROTOCOL=krb5";
+
+
+int main(int argc, char **argv) { 
+  
+  struct {
+    std::string eos_instance;
+    std::string eos_poweruser;
+    std::string eos_dir;
+    std::string subdir;
+    std::string file;
+    std::string error_dir;
+  } options;
+  
+  int opt_ret;
+  while (-1 != (opt_ret = getopt_long(argc, argv, short_options, long_options, nullptr))) {
+    switch (opt_ret) {
+    case (int)OptionIds::eos_instance:
+      options.eos_instance = optarg;
+      break;
+    case (int)OptionIds::eos_poweruser:
+      options.eos_poweruser = optarg;
+      break;
+    case (int)OptionIds::eos_dir:
+      options.eos_dir = optarg;
+      break;
+    case (int)OptionIds::subdir:
+      options.subdir = optarg;
+      break;
+    case (int)OptionIds::file:
+      options.file = optarg;
+      break;
+    case (int)OptionIds::error_dir:
+      options.error_dir = optarg;
+      break;
+    case '?':
+    default:
+      std::cerr << "Unexpected option or missing argument." << std::endl;
+      exit(EXIT_FAILURE);
+      break;
+    }
+  }
+  
+  if (options.eos_instance.empty() || options.eos_poweruser.empty() || options.error_dir.empty() || options.subdir.empty() ||
+      options.file.empty() || options.error_dir.empty()) {
+    std::cerr << "At least one option missing." << std::endl;
+    help();
+    exit (EXIT_FAILURE);
+  }
+  
+  std::cout << "To run again: "    << argv[0] 
+            << " --eos-instance="  << options.eos_instance
+            << " --eos-poweruser=" << options.eos_poweruser
+            << " --eos-dir="        << options.eos_dir
+            << " --subdir="         << options.subdir
+            << " --file="           << options.file
+            << " --error_dir="      << options.error_dir << std::endl;
+  
+  // Get the extended attribute for the retrieve request id
+  try {
+    // Prepare environment.
+    putenv(envXRD_LOGLEVEL);
+    std::string envKRB5CCNAMEvalue = std::string("KRB5CCNAME=/tmp/") + options.eos_poweruser + "/krb5cc_0";
+    // We need to copy to an array because of putenv's lack of const correctness.
+    envKRB5CCNAME.reset(new char[envKRB5CCNAMEvalue.size() + 1]);
+    strncpy(envKRB5CCNAME.get(), envKRB5CCNAMEvalue.c_str(), envKRB5CCNAMEvalue.size() + 1);
+    putenv(envKRB5CCNAME.get());
+    putenv(envXrdSecPROTOCOL);
+    
+    XrdCl::FileSystem xrdfs(options.eos_instance);
+    std::string fileName = options.eos_dir + "/" + options.subdir + "/" + options.file;
+    std::string query = fileName + "?mgm.pcmd=xattr&mgm.subcmd=get&mgm.xattrname=sys.retrieve.req_id";
+    auto qcOpaque = XrdCl::QueryCode::OpaqueFile;
+    XrdCl::Buffer xrdArg;
+    xrdArg.FromString(query);
+    XrdCl::Buffer *respPtr = nullptr;
+    auto status = xrdfs.Query(qcOpaque, xrdArg, respPtr, (uint16_t)0 /*timeout=default*/);
+    // Ensure proper memory management for the response buffer (it is our responsilibity to free it, we delegate to the unique_ptr).
+    std::unique_ptr<XrdCl::Buffer> respUP(respPtr);
+    respPtr = nullptr;
+    cta::exception::XrootCl::throwOnError(status, "Error during XrdCl::Query");
+    cta::utils::Regex re("value=(.*)");
+    std::string respStr(respUP->GetBuffer(), respUP->GetSize());
+    auto reResult = re.exec(respStr);
+    if (reResult.size() != 2) {
+      // We did not receive the expected structure
+      throw cta::exception::Exception(std::string("Unexpected result from xattr query: ") + respStr);
+    }
+    std::string retrieveRequestId = reResult[1];
+    std::vector<std::string> files = { retrieveRequestId, fileName };
+    XrdCl::PrepareFlags::Flags flags = XrdCl::PrepareFlags::Cancel;
+    auto abortStatus = xrdfs.Prepare(files, flags, 0, respPtr, 0 /* timeout */);
+    cta::exception::XrootCl::throwOnError(abortStatus, "Error during XrdCl::Prepare");
+  } catch (cta::exception::Exception & ex) {
+    std::cerr << "Received exception: " << ex.what() << std::endl;
+  }
+  return 0;
+}
diff --git a/continuousintegration/orchestration/tests/client_ar.sh b/continuousintegration/orchestration/tests/client_ar.sh
index f97a7d3be1..a404e1f781 100644
--- a/continuousintegration/orchestration/tests/client_ar.sh
+++ b/continuousintegration/orchestration/tests/client_ar.sh
@@ -359,6 +359,8 @@ for ((subdir=0; subdir < ${NB_DIRS}; subdir++)); do
 done
 
 # Put all tape drives down
+echo "Sleeping 3 seconds to let previous sessions finish."
+sleep 3
 admin_kdestroy &>/dev/null
 admin_kinit &>/dev/null
 INITIAL_DRIVES_STATE=`admin_cta --json dr ls`
@@ -403,7 +405,7 @@ fi
 for ((subdir=0; subdir < ${NB_DIRS}; subdir++)); do
   echo -n "Cancelling prepare for files in ${EOS_DIR}/${subdir} using ${NB_PROCS} processes (prepare_abort)..."
   cat ${STATUS_FILE} | grep ^${subdir}/ | cut -d/ -f2                                                                         \
-  | xargs --max-procs=${NB_PROCS} -iTEST_FILE_NAME /root/client_ar_abortPrepare.py --eos-instance ${EOSINSTANCE}              \
+  | xargs --max-procs=${NB_PROCS} -iTEST_FILE_NAME cta-client-ar-abortPrepare --eos-instance ${EOSINSTANCE}              \
       --eos-poweruser ${EOSPOWER_USER} --eos-dir ${EOS_DIR} --subdir ${subdir} --file TEST_FILE_NAME --error-dir ${ERROR_DIR} \
   | tee ${LOGDIR}/prepare_abort_sys.retrieve.req_id_${subdir}.log # | grep ^ERROR
   echo Done.
@@ -440,11 +442,12 @@ while test ${REMAINING_REQUESTS} -gt 0; do
 done
 
 # Check that the files were not retrieved
-echo "Checking restaged files."
+echo -n "Checking restaged files. Found: "
 RESTAGEDFILES=0
 for ((subdir=0; subdir < ${NB_DIRS}; subdir++)); do
-  RESTAGEDFILES=$(( ${RESTAGEDFILES} + $(eos root://${EOSINSTANCE} ls -y ${EOS_DIR}/${subdir} | egrep '^d[1-9][0-9]*::t1' | wc -l) ))
+  (( RESTAGEDFILES += $(eos root://${EOSINSTANCE} ls -y ${EOS_DIR}/${subdir} | egrep '^d[1-9][0-9]*::t1' | wc -l) ))
 done
+echo ${RESTAGEDFILES}
 
 if [ "0" != "$(ls ${ERROR_DIR} 2> /dev/null | wc -l)" ]; then
   # there were some prepare errors
@@ -453,7 +456,6 @@ if [ "0" != "$(ls ${ERROR_DIR} 2> /dev/null | wc -l)" ]; then
   mv ${ERROR_DIR}/* ${LOGDIR}/xrd_errors/
 fi
 
-
 # We can now delete the files
 DELETED=0
 if [[ $REMOVE == 1 ]]; then
diff --git a/cta.spec.in b/cta.spec.in
index f091c76fe4..bfd09d3d1c 100644
--- a/cta.spec.in
+++ b/cta.spec.in
@@ -410,3 +410,13 @@ collects EOS disk copies that have been safely stored to tape.
 %postun -n cta-fst-gcd
 %systemd_postun cta-fst-gcd.service
 %systemdDaemonReload
+
+%package -n cta-systemtest-helpers
+Summary: Collection of utilities deployed in system test client containers.
+Group: Application/CTA
+Requires: cta-lib = %{version}-%{release}
+%description -n cta-systemtest-helpers
+Collection of utilities deployed in system test client containers.
+Currently contains a helper for the client-ar script, which should be installed alongside it.
+%files -n cta-systemtest-helpers
+%attr(0755,root,root) /usr/bin/cta-client-ar-abortPrepare
-- 
GitLab