diff --git a/cmdline/CtaAdminCmd.cpp b/cmdline/CtaAdminCmd.cpp index 6279e19ed845ee6f2ab7d0a9d7a11989ab9b8e3c..631ceaa7584e15d76591a39968dda3340fbbaf8d 100644 --- a/cmdline/CtaAdminCmd.cpp +++ b/cmdline/CtaAdminCmd.cpp @@ -27,9 +27,14 @@ #include <cmdline/CtaAdminTextFormatter.hpp> -// global synchronisation flag between main thread and stream handler thread +// GLOBAL VARIABLES : used to pass information between main thread and stream handler thread + +// global synchronisation flag std::atomic<bool> isHeaderSent(false); +// initialise an output buffer of 1000 lines +cta::admin::TextFormatter formattedText(1000); + namespace XrdSsiPb { @@ -83,17 +88,17 @@ void IStreamBuffer<cta::xrd::Data>::DataCallback(cta::xrd::Data record) const } // Format results in a tabular format for a human else switch(record.data_case()) { - case Data::kAflsItem: CtaAdminTextFormatter::print(record.afls_item()); break; - case Data::kAflsSummary: CtaAdminTextFormatter::print(record.afls_summary()); break; - case Data::kFrlsItem: CtaAdminTextFormatter::print(record.frls_item()); break; - case Data::kFrlsSummary: CtaAdminTextFormatter::print(record.frls_summary()); break; - case Data::kLpaItem: CtaAdminTextFormatter::print(record.lpa_item()); break; - case Data::kLpaSummary: CtaAdminTextFormatter::print(record.lpa_summary()); break; - case Data::kLprItem: CtaAdminTextFormatter::print(record.lpr_item()); break; - case Data::kLprSummary: CtaAdminTextFormatter::print(record.lpr_summary()); break; - case Data::kTplsItem: CtaAdminTextFormatter::print(record.tpls_item()); break; - case Data::kTalsItem: CtaAdminTextFormatter::print(record.tals_item()); break; - case Data::kRelsItem: CtaAdminTextFormatter::print(record.rels_item()); break; + case Data::kAflsItem: formattedText.print(record.afls_item()); break; + case Data::kAflsSummary: formattedText.print(record.afls_summary()); break; + case Data::kFrlsItem: formattedText.print(record.frls_item()); break; + case Data::kFrlsSummary: formattedText.print(record.frls_summary()); break; + case Data::kLpaItem: formattedText.print(record.lpa_item()); break; + case Data::kLpaSummary: formattedText.print(record.lpa_summary()); break; + case Data::kLprItem: formattedText.print(record.lpr_item()); break; + case Data::kLprSummary: formattedText.print(record.lpr_summary()); break; + case Data::kTplsItem: formattedText.print(record.tpls_item()); break; + case Data::kTalsItem: formattedText.print(record.tals_item()); break; + case Data::kRelsItem: formattedText.print(record.rels_item()); break; default: throw std::runtime_error("Received invalid stream data from CTA Frontend."); } @@ -227,17 +232,17 @@ void CtaAdminCmd::send() const std::cout << response.message_txt(); // Print streaming response header if(!isJson()) switch(response.show_header()) { - case HeaderType::ARCHIVEFILE_LS: CtaAdminTextFormatter::printAfLsHeader(); break; - case HeaderType::ARCHIVEFILE_LS_SUMMARY: CtaAdminTextFormatter::printAfLsSummaryHeader(); break; - case HeaderType::FAILEDREQUEST_LS: CtaAdminTextFormatter::printFrLsHeader(); break; - case HeaderType::FAILEDREQUEST_LS_SUMMARY: CtaAdminTextFormatter::printFrLsSummaryHeader(); break; - case HeaderType::LISTPENDINGARCHIVES: CtaAdminTextFormatter::printLpaHeader(); break; - case HeaderType::LISTPENDINGARCHIVES_SUMMARY: CtaAdminTextFormatter::printLpaSummaryHeader(); break; - case HeaderType::LISTPENDINGRETRIEVES: CtaAdminTextFormatter::printLprHeader(); break; - case HeaderType::LISTPENDINGRETRIEVES_SUMMARY: CtaAdminTextFormatter::printLprSummaryHeader(); break; - case HeaderType::TAPEPOOL_LS: CtaAdminTextFormatter::printTpLsHeader(); break; - case HeaderType::TAPE_LS: CtaAdminTextFormatter::printTapeLsHeader(); break; - case HeaderType::REPACK_LS: CtaAdminTextFormatter::printRepackLsHeader(); break; + case HeaderType::ARCHIVEFILE_LS: formattedText.printAfLsHeader(); break; + case HeaderType::ARCHIVEFILE_LS_SUMMARY: formattedText.printAfLsSummaryHeader(); break; + case HeaderType::FAILEDREQUEST_LS: formattedText.printFrLsHeader(); break; + case HeaderType::FAILEDREQUEST_LS_SUMMARY: formattedText.printFrLsSummaryHeader(); break; + case HeaderType::LISTPENDINGARCHIVES: formattedText.printLpaHeader(); break; + case HeaderType::LISTPENDINGARCHIVES_SUMMARY: formattedText.printLpaSummaryHeader(); break; + case HeaderType::LISTPENDINGRETRIEVES: formattedText.printLprHeader(); break; + case HeaderType::LISTPENDINGRETRIEVES_SUMMARY: formattedText.printLprSummaryHeader(); break; + case HeaderType::TAPEPOOL_LS: formattedText.printTapePoolLsHeader(); break; + case HeaderType::TAPE_LS: formattedText.printTapeLsHeader(); break; + case HeaderType::REPACK_LS: formattedText.printRepackLsHeader(); break; case HeaderType::NONE: default: break; } diff --git a/cmdline/CtaAdminTextFormatter.cpp b/cmdline/CtaAdminTextFormatter.cpp index 2c344e9a93430320795b7ed487a4c00e7ac71cf9..fe579e5aad613664cff0269e37e63b5864194301 100644 --- a/cmdline/CtaAdminTextFormatter.cpp +++ b/cmdline/CtaAdminTextFormatter.cpp @@ -24,397 +24,457 @@ namespace cta { namespace admin { -void CtaAdminTextFormatter::printAfLsHeader() -{ - std::cout << TEXT_RED - << std::setfill(' ') << std::setw(11) << std::right << "archive id" << ' ' - << std::setfill(' ') << std::setw(7) << std::right << "copy no" << ' ' - << std::setfill(' ') << std::setw(7) << std::right << "vid" << ' ' - << std::setfill(' ') << std::setw(7) << std::right << "fseq" << ' ' - << std::setfill(' ') << std::setw(8) << std::right << "block id" << ' ' - << std::setfill(' ') << std::setw(8) << std::right << "instance" << ' ' - << std::setfill(' ') << std::setw(7) << std::right << "disk id" << ' ' - << std::setfill(' ') << std::setw(12) << std::right << "size" << ' ' - << std::setfill(' ') << std::setw(13) << std::right << "checksum type" << ' ' - << std::setfill(' ') << std::setw(14) << std::right << "checksum value" << ' ' - << std::setfill(' ') << std::setw(16) << std::right << "storage class" << ' ' - << std::setfill(' ') << std::setw(8) << std::right << "owner" << ' ' - << std::setfill(' ') << std::setw(8) << std::right << "group" << ' ' - << std::setfill(' ') << std::setw(13) << std::right << "creation time" << ' ' - << std::setfill(' ') << std::setw(7) << std::right << "ss vid" << ' ' - << std::setfill(' ') << std::setw(7) << std::right << "ss fseq" << ' ' - << "path" - << TEXT_NORMAL << std::endl; +std::string TextFormatter::doubleToStr(double value, char unit) { + std::stringstream ss; + ss << std::fixed << std::setprecision(1) << value << unit; + return ss.str(); } -void CtaAdminTextFormatter::print(const cta::admin::ArchiveFileLsItem &afls_item) -{ - std::cout << std::setfill(' ') << std::setw(11) << std::right << afls_item.af().archive_id() << ' ' - << std::setfill(' ') << std::setw(7) << std::right << afls_item.copy_nb() << ' ' - << std::setfill(' ') << std::setw(7) << std::right << afls_item.tf().vid() << ' ' - << std::setfill(' ') << std::setw(7) << std::right << afls_item.tf().f_seq() << ' ' - << std::setfill(' ') << std::setw(8) << std::right << afls_item.tf().block_id() << ' ' - << std::setfill(' ') << std::setw(8) << std::right << afls_item.af().disk_instance() << ' ' - << std::setfill(' ') << std::setw(7) << std::right << afls_item.af().disk_id() << ' ' - << std::setfill(' ') << std::setw(12) << std::right << afls_item.af().size() << ' ' - << std::setfill(' ') << std::setw(13) << std::right << afls_item.af().cs().type() << ' ' - << std::setfill(' ') << std::setw(14) << std::right << afls_item.af().cs().value() << ' ' - << std::setfill(' ') << std::setw(16) << std::right << afls_item.af().storage_class() << ' ' - << std::setfill(' ') << std::setw(8) << std::right << afls_item.af().df().owner() << ' ' - << std::setfill(' ') << std::setw(8) << std::right << afls_item.af().df().group() << ' ' - << std::setfill(' ') << std::setw(13) << std::right << afls_item.af().creation_time() << ' '; - - if (afls_item.tf().superseded_by_vid().size()) { - std::cout << std::setfill(' ') << std::setw(7) << std::right << afls_item.tf().superseded_by_vid() << ' ' - << std::setfill(' ') << std::setw(7) << std::right << afls_item.tf().superseded_by_f_seq() << ' '; - } else { - std::cout << std::setfill(' ') << std::setw(7) << std::right << "-" << ' ' - << std::setfill(' ') << std::setw(7) << std::right << "-" << ' '; - } - std::cout << afls_item.af().df().path() - << std::endl; + +std::string TextFormatter::timeToStr(const time_t &unixtime) { + struct tm timeTm; + localtime_r(&unixtime, &timeTm); + + char timeStr[17]; // YYYY-MM-DD HH:MM + strftime(timeStr, 17, "%F %R", &timeTm); + + return timeStr; } -void CtaAdminTextFormatter::printAfLsSummaryHeader() -{ - std::cout << TEXT_RED - << std::setfill(' ') << std::setw(13) << std::right << "total files" << ' ' - << std::setfill(' ') << std::setw(12) << std::right << "total size" << ' ' - << TEXT_NORMAL << std::endl; + +std::string TextFormatter::dataSizeToStr(uint64_t value) { + const std::vector<char> suffix = { 'K', 'M', 'G', 'T', 'P', 'E' }; + + // Simple case, values less than 1000 bytes don't take a suffix + if(value < 1000) return std::to_string(value); + + // Find the correct scaling, starting at 1 KB and working up. I'm assuming we won't have zettabytes + // or yottabytes of data in a tapepool anytime soon. + int unit; + uint64_t divisor; + for(unit = 0, divisor = 1000; unit < 6 && value >= divisor*1000; divisor *= 1000, ++unit) ; + + // Convert to format like "3.1G" + double val_d = static_cast<double>(value) / static_cast<double>(divisor); + return doubleToStr(val_d, suffix[unit]); } -void CtaAdminTextFormatter::print(const cta::admin::ArchiveFileLsSummary &afls_summary) -{ - std::cout << std::setfill(' ') << std::setw(13) << std::right << afls_summary.total_files() << ' ' - << std::setfill(' ') << std::setw(12) << std::right << afls_summary.total_size() << ' ' - << std::endl; + +void TextFormatter::flush() { + if(m_outputBuffer.empty()) return; + + // Check if first line is a header requiring special formatting + bool is_header = false; + if(m_outputBuffer.front().size() == 1 && m_outputBuffer.front().front() == "HEADER") { + is_header = true; + m_outputBuffer.erase(m_outputBuffer.begin()); + if(m_outputBuffer.empty()) return; + } + + auto numCols = m_outputBuffer.front().size(); + std::vector<unsigned int> colSize(numCols); + + // Calculate column widths + for(auto &l : m_outputBuffer) { + if(l.size() != numCols) throw std::runtime_error("TextFormatter::flush(): incorrect number of columns"); + for(size_t c = 0; c < l.size(); ++c) { + if(colSize.at(c) < l.at(c).size()) colSize[c] = l.at(c).size(); + } + } + + // Output columns + for(auto &l : m_outputBuffer) { + if(is_header) { std::cout << TEXT_RED; } + for(size_t c = 0; c < l.size(); ++c) { + std::cout << std::setfill(' ') + << std::setw(colSize.at(c)+1) + << std::right + << (l.at(c).empty() ? "-" : l.at(c)) + << ' '; + } + if(is_header) { std::cout << TEXT_NORMAL; is_header = false; } + std::cout << std::endl; + } + + // Empty buffer + m_outputBuffer.clear(); } -void CtaAdminTextFormatter::printFrLsHeader() -{ - std::cout << TEXT_RED - << std::setfill(' ') << std::setw(12) << std::right << "request type" << ' ' - << std::setfill(' ') << std::setw(8) << std::right << "copy no" << ' ' - << std::setfill(' ') << std::setw(13) << std::right << "tapepool/vid" << ' ' - << std::setfill(' ') << std::setw(10) << std::right << "requester" << ' ' - << std::setfill(' ') << std::setw(6) << std::right << "group" << ' ' - << "path" - << TEXT_NORMAL << std::endl; + +void TextFormatter::printAfLsHeader() { + push_back("HEADER"); + push_back( + "archive id", + "copy no", + "vid", + "fseq", + "block id", + "instance", + "disk id", + "size", + "checksum type", + "checksum value", + "storage class", + "owner", + "group", + "creation time", + "sc vid", // superceded + "sc fseq", + "path" + ); } -void CtaAdminTextFormatter::print(const cta::admin::FailedRequestLsItem &frls_item) -{ - std::string request_type; - std::string tapepool_vid; - - switch(frls_item.request_type()) { - case admin::RequestType::ARCHIVE_REQUEST: - request_type = "archive"; - tapepool_vid = frls_item.tapepool(); - break; - case admin::RequestType::RETRIEVE_REQUEST: - request_type = "retrieve"; - tapepool_vid = frls_item.tf().vid(); - break; - default: - throw std::runtime_error("Unrecognised request type: " + std::to_string(frls_item.request_type())); - } - - std::cout << std::setfill(' ') << std::setw(11) << std::right << request_type << ' ' - << std::setfill(' ') << std::setw(8) << std::right << frls_item.copy_nb() << ' ' - << std::setfill(' ') << std::setw(14) << std::right << tapepool_vid << ' ' - << std::setfill(' ') << std::setw(10) << std::right << frls_item.requester().username() << ' ' - << std::setfill(' ') << std::setw(6) << std::right << frls_item.requester().groupname() << ' ' - << frls_item.af().df().path() - << std::endl; - - for(auto &errLogMsg : frls_item.failurelogs()) { - std::cout << errLogMsg << std::endl; - } +void TextFormatter::print(const cta::admin::ArchiveFileLsItem &afls_item) { + push_back( + afls_item.af().archive_id(), + afls_item.copy_nb(), + afls_item.tf().vid(), + afls_item.tf().f_seq(), + afls_item.tf().block_id(), + afls_item.af().disk_instance(), + afls_item.af().disk_id(), + dataSizeToStr(afls_item.af().size()), + afls_item.af().cs().type(), + afls_item.af().cs().value(), + afls_item.af().storage_class(), + afls_item.af().df().owner(), + afls_item.af().df().group(), + timeToStr(afls_item.af().creation_time()), + afls_item.tf().superseded_by_vid(), + afls_item.tf().superseded_by_f_seq(), + afls_item.af().df().path() + ); } -void CtaAdminTextFormatter::printFrLsSummaryHeader() -{ - std::cout << TEXT_RED - << std::setfill(' ') << std::setw(12) << std::right << "request type" << ' ' - << std::setfill(' ') << std::setw(13) << std::right << "total files" << ' ' - << std::setfill(' ') << std::setw(20) << std::right << "total size (bytes)" << ' ' - << TEXT_NORMAL << std::endl; +void TextFormatter::printAfLsSummaryHeader() { + push_back("HEADER"); + push_back( + "total files", + "total size" + ); } -void CtaAdminTextFormatter::print(const cta::admin::FailedRequestLsSummary &frls_summary) +void TextFormatter::print(const cta::admin::ArchiveFileLsSummary &afls_summary) { - std::string request_type = - frls_summary.request_type() == cta::admin::RequestType::ARCHIVE_REQUEST ? "archive" : - frls_summary.request_type() == cta::admin::RequestType::RETRIEVE_REQUEST ? "retrieve" : "total"; - - std::cout << std::setfill(' ') << std::setw(11) << std::right << request_type << ' ' - << std::setfill(' ') << std::setw(13) << std::right << frls_summary.total_files() << ' ' - << std::setfill(' ') << std::setw(20) << std::right << frls_summary.total_size() << ' ' - << std::endl; + push_back( + afls_summary.total_files(), + dataSizeToStr(afls_summary.total_size()) + ); } -void CtaAdminTextFormatter::printLpaHeader() -{ - std::cout << TEXT_RED - << std::setfill(' ') << std::setw(18) << std::right << "tapepool" << ' ' - << std::setfill(' ') << std::setw(11) << std::right << "archive id" << ' ' - << std::setfill(' ') << std::setw(13) << std::right << "storage class" << ' ' - << std::setfill(' ') << std::setw(7) << std::right << "copy no" << ' ' - << std::setfill(' ') << std::setw(7) << std::right << "disk id" << ' ' - << std::setfill(' ') << std::setw(8) << std::right << "instance" << ' ' - << std::setfill(' ') << std::setw(13) << std::right << "checksum type" << ' ' - << std::setfill(' ') << std::setw(14) << std::right << "checksum value" << ' ' - << std::setfill(' ') << std::setw(12) << std::right << "size" << ' ' - << std::setfill(' ') << std::setw(8) << std::right << "user" << ' ' - << std::setfill(' ') << std::setw(8) << std::right << "group" << ' ' - << "path" - << TEXT_NORMAL << std::endl; +void TextFormatter::printFrLsHeader() { + push_back("HEADER"); + push_back( + "request type", + "copy no", + "tapepool/vid", + "requester", + "group", + "path" + ); } -void CtaAdminTextFormatter::print(const cta::admin::ListPendingArchivesItem &lpa_item) -{ - std::cout << std::setfill(' ') << std::setw(18) << std::right << lpa_item.tapepool() << ' ' - << std::setfill(' ') << std::setw(11) << std::right << lpa_item.af().archive_id() << ' ' - << std::setfill(' ') << std::setw(13) << std::right << lpa_item.af().storage_class() << ' ' - << std::setfill(' ') << std::setw(7) << std::right << lpa_item.copy_nb() << ' ' - << std::setfill(' ') << std::setw(7) << std::right << lpa_item.af().disk_id() << ' ' - << std::setfill(' ') << std::setw(8) << std::right << lpa_item.af().disk_instance() << ' ' - << std::setfill(' ') << std::setw(13) << std::right << lpa_item.af().cs().type() << ' ' - << std::setfill(' ') << std::setw(14) << std::right << lpa_item.af().cs().value() << ' ' - << std::setfill(' ') << std::setw(12) << std::right << lpa_item.af().size() << ' ' - << std::setfill(' ') << std::setw(8) << std::right << lpa_item.af().df().owner() << ' ' - << std::setfill(' ') << std::setw(8) << std::right << lpa_item.af().df().group() << ' ' - << lpa_item.af().df().path() - << std::endl; +void TextFormatter::print(const cta::admin::FailedRequestLsItem &frls_item) { + std::string request_type; + std::string tapepool_vid; + + switch(frls_item.request_type()) { + case admin::RequestType::ARCHIVE_REQUEST: + request_type = "archive"; + tapepool_vid = frls_item.tapepool(); + break; + case admin::RequestType::RETRIEVE_REQUEST: + request_type = "retrieve"; + tapepool_vid = frls_item.tf().vid(); + break; + default: + throw std::runtime_error("Unrecognised request type: " + std::to_string(frls_item.request_type())); + } + + push_back( + request_type, + frls_item.copy_nb(), + tapepool_vid, + frls_item.requester().username(), + frls_item.requester().groupname(), + frls_item.af().df().path() + ); + + // Note: failure log messages are available in frls_item.failurelogs(). These are not currently + // displayed in the text output, only in JSON. } -void CtaAdminTextFormatter::printLpaSummaryHeader() -{ - std::cout << TEXT_RED - << std::setfill(' ') << std::setw(18) << std::right << "tapepool" << ' ' - << std::setfill(' ') << std::setw(13) << std::right << "total files" << ' ' - << std::setfill(' ') << std::setw(12) << std::right << "total size" << ' ' - << TEXT_NORMAL << std::endl; +void TextFormatter::printFrLsSummaryHeader() { + push_back("HEADER"); + push_back( + "request type", + "total files", + "total size" + ); } -void CtaAdminTextFormatter::print(const cta::admin::ListPendingArchivesSummary &lpa_summary) -{ - std::cout << std::setfill(' ') << std::setw(18) << std::right << lpa_summary.tapepool() << ' ' - << std::setfill(' ') << std::setw(13) << std::right << lpa_summary.total_files() << ' ' - << std::setfill(' ') << std::setw(12) << std::right << lpa_summary.total_size() << ' ' - << std::endl; +void TextFormatter::print(const cta::admin::FailedRequestLsSummary &frls_summary) { + std::string request_type = + frls_summary.request_type() == cta::admin::RequestType::ARCHIVE_REQUEST ? "archive" : + frls_summary.request_type() == cta::admin::RequestType::RETRIEVE_REQUEST ? "retrieve" : "total"; + + push_back( + request_type, + frls_summary.total_files(), + dataSizeToStr(frls_summary.total_size()) + ); } -void CtaAdminTextFormatter::printLprHeader() -{ - std::cout << TEXT_RED - << std::setfill(' ') << std::setw(13) << std::right << "vid" << ' ' - << std::setfill(' ') << std::setw(11) << std::right << "archive id" << ' ' - << std::setfill(' ') << std::setw(7) << std::right << "copy no" << ' ' - << std::setfill(' ') << std::setw(7) << std::right << "fseq" << ' ' - << std::setfill(' ') << std::setw(9) << std::right << "block id" << ' ' - << std::setfill(' ') << std::setw(12) << std::right << "size" << ' ' - << std::setfill(' ') << std::setw(8) << std::right << "user" << ' ' - << std::setfill(' ') << std::setw(8) << std::right << "group" << ' ' - << "path" - << TEXT_NORMAL << std::endl; +void TextFormatter::printLpaHeader() { + push_back("HEADER"); + push_back( + "tapepool", + "archive id", + "storage class", + "copy no", + "disk id", + "instance", + "checksum type", + "checksum value", + "size", + "user", + "group", + "path" + ); } -void CtaAdminTextFormatter::print(const cta::admin::ListPendingRetrievesItem &lpr_item) -{ - std::cout << std::setfill(' ') << std::setw(13) << std::right << lpr_item.tf().vid() << ' ' - << std::setfill(' ') << std::setw(11) << std::right << lpr_item.af().archive_id() << ' ' - << std::setfill(' ') << std::setw(7) << std::right << lpr_item.copy_nb() << ' ' - << std::setfill(' ') << std::setw(7) << std::right << lpr_item.tf().f_seq() << ' ' - << std::setfill(' ') << std::setw(9) << std::right << lpr_item.tf().block_id() << ' ' - << std::setfill(' ') << std::setw(12) << std::right << lpr_item.af().size() << ' ' - << std::setfill(' ') << std::setw(8) << std::right << lpr_item.af().df().owner() << ' ' - << std::setfill(' ') << std::setw(8) << std::right << lpr_item.af().df().group() << ' ' - << lpr_item.af().df().path() - << std::endl; +void TextFormatter::print(const cta::admin::ListPendingArchivesItem &lpa_item) { + push_back( + lpa_item.tapepool(), + lpa_item.af().archive_id(), + lpa_item.af().storage_class(), + lpa_item.copy_nb(), + lpa_item.af().disk_id(), + lpa_item.af().disk_instance(), + lpa_item.af().cs().type(), + lpa_item.af().cs().value(), + dataSizeToStr(lpa_item.af().size()), + lpa_item.af().df().owner(), + lpa_item.af().df().group(), + lpa_item.af().df().path() + ); } -void CtaAdminTextFormatter::printLprSummaryHeader() -{ - std::cout << TEXT_RED - << std::setfill(' ') << std::setw(13) << std::right << "vid" << ' ' - << std::setfill(' ') << std::setw(13) << std::right << "total files" << ' ' - << std::setfill(' ') << std::setw(12) << std::right << "total size" << ' ' - << TEXT_NORMAL << std::endl; +void TextFormatter::printLpaSummaryHeader() { + push_back("HEADER"); + push_back( + "tapepool", + "total files", + "total size" + ); } -void CtaAdminTextFormatter::print(const cta::admin::ListPendingRetrievesSummary &lpr_summary) -{ - std::cout << std::setfill(' ') << std::setw(13) << std::right << lpr_summary.vid() << ' ' - << std::setfill(' ') << std::setw(13) << std::right << lpr_summary.total_files() << ' ' - << std::setfill(' ') << std::setw(12) << std::right << lpr_summary.total_size() << ' ' - << std::endl; +void TextFormatter::print(const cta::admin::ListPendingArchivesSummary &lpa_summary) { + push_back( + lpa_summary.tapepool(), + lpa_summary.total_files(), + dataSizeToStr(lpa_summary.total_size()) + ); } -void CtaAdminTextFormatter::printTpLsHeader() -{ - std::cout << TEXT_RED - << std::setfill(' ') << std::setw(18) << std::right << "name" << ' ' - << std::setfill(' ') << std::setw(10) << std::right << "vo" << ' ' - << std::setfill(' ') << std::setw(7) << std::right << "#tapes" << ' ' - << std::setfill(' ') << std::setw(9) << std::right << "#partial" << ' ' - << std::setfill(' ') << std::setw(12) << std::right << "#phys files" << ' ' - << std::setfill(' ') << std::setw(5) << std::right << "size" << ' ' - << std::setfill(' ') << std::setw(5) << std::right << "used" << ' ' - << std::setfill(' ') << std::setw(6) << std::right << "avail" << ' ' - << std::setfill(' ') << std::setw(6) << std::right << "use%" << ' ' - << std::setfill(' ') << std::setw(8) << std::right << "encrypt" << ' ' - << std::setfill(' ') << std::setw(20) << std::right << "supply" << ' ' - << std::setfill(' ') << std::setw(8) << std::right << "c.user" << ' ' - << std::setfill(' ') << std::setw(25) << std::right << "c.host" << ' ' - << std::setfill(' ') << std::setw(24) << std::right << "c.time" << ' ' - << std::setfill(' ') << std::setw(8) << std::right << "m.user" << ' ' - << std::setfill(' ') << std::setw(25) << std::right << "m.host" << ' ' - << std::setfill(' ') << std::setw(24) << std::right << "m.time" << ' ' - << "comment" << ' ' - << TEXT_NORMAL << std::endl; +void TextFormatter::printLprHeader() { + push_back("HEADER"); + push_back( + "vid", + "archive id", + "copy no", + "fseq", + "block id", + "size", + "user", + "group", + "path" + ); } -void CtaAdminTextFormatter::printTapeLsHeader(){ - std::cout << TEXT_RED - << std::setfill(' ') << std::setw(7) << std::right << "vid" << ' ' - << std::setfill(' ') << std::setw(10) << std::right << "media type" << ' ' - << std::setfill(' ') << std::setw(7) << std::right << "vendor" << ' ' - << std::setfill(' ') << std::setw(20) << std::right << "logical library" << ' ' - << std::setfill(' ') << std::setw(18) << std::right << "tapepool" << ' ' - << std::setfill(' ') << std::setw(10) << std::right << "vo" << ' ' - << std::setfill(' ') << std::setw(20) << std::right << "encryption key" << ' ' - << std::setfill(' ') << std::setw(12) << std::right << "capacity" << ' ' - << std::setfill(' ') << std::setw(12) << std::right << "occupancy" << ' ' - << std::setfill(' ') << std::setw(9) << std::right << "last fseq" << ' ' - << std::setfill(' ') << std::setw(5) << std::right << "full" << ' ' - << std::setfill(' ') << std::setw(8) << std::right << "disabled" << ' ' - << std::setfill(' ') << std::setw(12) << std::right << "label drive" << ' ' - << std::setfill(' ') << std::setw(12) << std::right << "label time" << ' ' - << std::setfill(' ') << std::setw(12) << std::right << "last w drive" << ' ' - << std::setfill(' ') << std::setw(12) << std::right << "last w time" << ' ' - << std::setfill(' ') << std::setw(12) << std::right << "last r drive" << ' ' - << std::setfill(' ') << std::setw(12) << std::right << "last r time" << ' ' - << std::setfill(' ') << std::setw(20) << std::right << "c.user" << ' ' - << std::setfill(' ') << std::setw(25) << std::right << "c.host" << ' ' - << std::setfill(' ') << std::setw(13) << std::right << "c.time" << ' ' - << std::setfill(' ') << std::setw(20) << std::right << "m.user" << ' ' - << std::setfill(' ') << std::setw(25) << std::right << "m.host" << ' ' - << std::setfill(' ') << std::setw(13) << std::right << "m.time" << ' ' - << "comment" << ' ' - << TEXT_NORMAL << std::endl; +void TextFormatter::print(const cta::admin::ListPendingRetrievesItem &lpr_item) { + push_back( + lpr_item.tf().vid(), + lpr_item.af().archive_id(), + lpr_item.copy_nb(), + lpr_item.tf().f_seq(), + lpr_item.tf().block_id(), + dataSizeToStr(lpr_item.af().size()), + lpr_item.af().df().owner(), + lpr_item.af().df().group(), + lpr_item.af().df().path() + ); } +void TextFormatter::printLprSummaryHeader() { + push_back("HEADER"); + push_back( + "vid", + "total files", + "total size" + ); +} -void CtaAdminTextFormatter::print(const cta::admin::TapeLsItem &tals_item){ - std::cout << std::setfill(' ') << std::setw(7) << std::right << tals_item.vid() << ' ' - << std::setfill(' ') << std::setw(10) << std::right << tals_item.media_type() << ' ' - << std::setfill(' ') << std::setw(7) << std::right << tals_item.vendor() << ' ' - << std::setfill(' ') << std::setw(20) << std::right << tals_item.logical_library() << ' ' - << std::setfill(' ') << std::setw(18) << std::right << tals_item.tapepool() << ' ' - << std::setfill(' ') << std::setw(10) << std::right << tals_item.vo() << ' ' - << std::setfill(' ') << std::setw(20) << std::right << tals_item.encryption_key() << ' ' - << std::setfill(' ') << std::setw(12) << std::right << tals_item.capacity() << ' ' - << std::setfill(' ') << std::setw(12) << std::right << tals_item.occupancy() << ' ' - << std::setfill(' ') << std::setw(9) << std::right << tals_item.last_fseq() << ' ' - << std::setfill(' ') << std::setw(5) << std::right << tals_item.full() << ' ' - << std::setfill(' ') << std::setw(8) << std::right << tals_item.disabled() << ' '; - if(tals_item.has_label_log()){ - std::cout << std::setfill(' ') << std::setw(12) << std::right << tals_item.label_log().drive() << ' ' - << std::setfill(' ') << std::setw(12) << std::right << tals_item.label_log().time() << ' '; - } else { - std::cout << std::setfill(' ') << std::setw(12) << std::right << "-" << ' ' - << std::setfill(' ') << std::setw(12) << std::right << "-" << ' '; - } - if(tals_item.has_last_written_log()){ - std::cout << std::setfill(' ') << std::setw(12) << std::right << tals_item.last_written_log().drive() << ' ' - << std::setfill(' ') << std::setw(12) << std::right << tals_item.last_written_log().time() << ' '; - } else { - std::cout << std::setfill(' ') << std::setw(12) << "-" << ' ' - << std::setfill(' ') << std::setw(12) << "-" << ' '; - } - if(tals_item.has_last_read_log()){ - std::cout << std::setfill(' ') << std::setw(12) << std::right << tals_item.last_read_log().drive() << ' ' - << std::setfill(' ') << std::setw(12) << std::right << tals_item.last_read_log().time() << ' '; - } else { - std::cout << std::setfill(' ') << std::setw(12) << std::right << "-" << ' ' - << std::setfill(' ') << std::setw(12) << std::right << "-" << ' '; - } - std::cout << std::setfill(' ') << std::setw(20) << std::right << tals_item.creation_log().username() << ' ' - << std::setfill(' ') << std::setw(25) << std::right << tals_item.creation_log().host() << ' ' - << std::setfill(' ') << std::setw(13) << std::right << tals_item.creation_log().time() << ' ' - << std::setfill(' ') << std::setw(20) << std::right << tals_item.last_modification_log().username() << ' ' - << std::setfill(' ') << std::setw(25) << std::right << tals_item.last_modification_log().host() << ' ' - << std::setfill(' ') << std::setw(13) << std::right << tals_item.last_modification_log().time() << ' ' - << std::endl; +void TextFormatter::print(const cta::admin::ListPendingRetrievesSummary &lpr_summary) { + push_back( + lpr_summary.vid(), + lpr_summary.total_files(), + dataSizeToStr(lpr_summary.total_size()) + ); +} + +void TextFormatter::printTapeLsHeader() { + push_back("HEADER"); + push_back( + "vid", + "media type", + "vendor", + "logical library", + "tapepool", + "vo", + "encryption key", + "capacity", + "occupancy", + "last fseq", + "full", + "disabled", + "label drive", + "label time", + "last w drive", + "last w time", + "last r drive", + "last r time", + "c.user", + "c.host", + "c.time", + "m.user", + "m.host", + "m.time" + ); +} + +void TextFormatter::print(const cta::admin::TapeLsItem &tals_item) { + push_back( + tals_item.vid(), + tals_item.media_type(), + tals_item.vendor(), + tals_item.logical_library(), + tals_item.tapepool(), + tals_item.vo(), + tals_item.encryption_key(), + dataSizeToStr(tals_item.capacity()), + dataSizeToStr(tals_item.occupancy()), + tals_item.last_fseq(), + tals_item.full(), + tals_item.disabled(), + tals_item.has_label_log() ? tals_item.label_log().drive() : "", + tals_item.has_label_log() ? timeToStr(tals_item.label_log().time()) : "", + tals_item.has_last_written_log() ? tals_item.last_written_log().drive() : "", + tals_item.has_last_written_log() ? timeToStr(tals_item.last_written_log().time()) : "", + tals_item.has_last_read_log() ? tals_item.last_read_log().drive() : "", + tals_item.has_last_read_log() ? timeToStr(tals_item.last_read_log().time()) : "", + tals_item.creation_log().username(), + tals_item.creation_log().host(), + timeToStr(tals_item.creation_log().time()), + tals_item.last_modification_log().username(), + tals_item.last_modification_log().host(), + timeToStr(tals_item.last_modification_log().time()) + ); +} + +void TextFormatter::printRepackLsHeader() { + push_back("HEADER"); + push_back( + "vid", + "repackBufferURL", + "userProvidedFiles", + "totalFilesToRetrieve", + "totalBytesToRetrieve", + "totalFilesToArchive", + "totalBytesToArchive", + "retrievedFiles", + "archivedFiles", + "failedToRetrieveFiles", + "failedToRetrieveBytes", + "failedToArchiveFiles", + "failedToArchiveBytes", + "lastExpandedFSeq", + "status" + ); } -void CtaAdminTextFormatter::printRepackLsHeader(){ - std::cout << TEXT_RED - << std::setfill(' ') << std::setw(7) << std::right << "vid" << ' ' - << std::setfill(' ') << std::setw(50) << std::right << "repackBufferURL" << ' ' - << std::setfill(' ') << std::setw(17) << std::right << "userProvidedFiles" << ' ' - << std::setfill(' ') << std::setw(20) << std::right << "totalFilesToRetrieve" << ' ' - << std::setfill(' ') << std::setw(19) << std::right << "totalBytesToRetrieve" << ' ' - << std::setfill(' ') << std::setw(20) << std::right << "totalFilesToArchive" << ' ' - << std::setfill(' ') << std::setw(19) << std::right << "totalBytesToArchive" << ' ' - << std::setfill(' ') << std::setw(14) << std::right << "retrievedFiles" << ' ' - << std::setfill(' ') << std::setw(13) << std::right << "archivedFiles" << ' ' - << std::setfill(' ') << std::setw(21) << std::right << "failedToRetrieveFiles" << ' ' - << std::setfill(' ') << std::setw(20) << std::right << "failedToRetrieveBytes" << ' ' - << std::setfill(' ') << std::setw(20) << std::right << "failedToArchiveFiles" << ' ' - << std::setfill(' ') << std::setw(20) << std::right << "failedToArchiveBytes" << ' ' - << std::setfill(' ') << std::setw(16) << std::right << "lastExpandedFSeq" << ' ' - << "status" << ' ' - << TEXT_NORMAL << std::endl; +void TextFormatter::print(const cta::admin::RepackLsItem &rels_item) { + push_back( + rels_item.vid(), + rels_item.repack_buffer_url(), + rels_item.user_provided_files(), + rels_item.total_files_to_retrieve(), + dataSizeToStr(rels_item.total_bytes_to_retrieve()), + rels_item.total_files_to_archive(), + dataSizeToStr(rels_item.total_bytes_to_archive()), + rels_item.retrieved_files(), + rels_item.archived_files(), + rels_item.failed_to_retrieve_files(), + dataSizeToStr(rels_item.failed_to_retrieve_bytes()), + rels_item.failed_to_archive_files(), + dataSizeToStr(rels_item.failed_to_retrieve_bytes()), + rels_item.last_expanded_fseq(), + rels_item.status() + ); } -void CtaAdminTextFormatter::print(const cta::admin::RepackLsItem &rels_item){ - std::cout << std::setfill(' ') << std::setw(7) << std::right << rels_item.vid() << ' ' - << std::setfill(' ') << std::setw(50) << std::right << rels_item.repack_buffer_url() << ' ' - << std::setfill(' ') << std::setw(17) << std::right << rels_item.user_provided_files() << ' ' - << std::setfill(' ') << std::setw(20) << std::right << rels_item.total_files_to_retrieve() << ' ' - << std::setfill(' ') << std::setw(19) << std::right << rels_item.total_bytes_to_retrieve() << ' ' - << std::setfill(' ') << std::setw(20) << std::right << rels_item.total_files_to_archive() << ' ' - << std::setfill(' ') << std::setw(19) << std::right << rels_item.total_bytes_to_archive() << ' ' - << std::setfill(' ') << std::setw(14) << std::right << rels_item.retrieved_files() << ' ' - << std::setfill(' ') << std::setw(13) << std::right << rels_item.archived_files() << ' ' - << std::setfill(' ') << std::setw(21) << std::right << rels_item.failed_to_retrieve_files() << ' ' - << std::setfill(' ') << std::setw(20) << std::right << rels_item.failed_to_retrieve_bytes() << ' ' - << std::setfill(' ') << std::setw(20) << std::right << rels_item.failed_to_archive_files() << ' ' - << std::setfill(' ') << std::setw(20) << std::right << rels_item.failed_to_retrieve_bytes() << ' ' - << std::setfill(' ') << std::setw(10) << std::right << rels_item.last_expanded_fseq() << ' ' - << rels_item.status() << std::endl; +void TextFormatter::printTapePoolLsHeader() { + push_back("HEADER"); + push_back( + "name", + "vo", + "#tapes", + "#partial", + "#phys files", + "size", + "used", + "avail", + "use%", + "encrypt", + "supply", + "c.user", + "c.host", + "c.time", + "m.user", + "m.host", + "m.time", + "comment" + ); } -void CtaAdminTextFormatter::print(const cta::admin::TapePoolLsItem &tpls_item) +void TextFormatter::print(const cta::admin::TapePoolLsItem &tpls_item) { - std::string encrypt_str = tpls_item.encrypt() ? "true" : "false"; - uint64_t avail = tpls_item.capacity_bytes() > tpls_item.data_bytes() ? - tpls_item.capacity_bytes()-tpls_item.data_bytes() : 0; - double use_percent = tpls_item.capacity_bytes() > 0 ? - (static_cast<double>(tpls_item.data_bytes())/static_cast<double>(tpls_item.capacity_bytes()))*100.0 : 0.0; - - std::cout << std::setfill(' ') << std::setw(18) << std::right << tpls_item.name() << ' ' - << std::setfill(' ') << std::setw(10) << std::right << tpls_item.vo() << ' ' - << std::setfill(' ') << std::setw(7) << std::right << tpls_item.num_tapes() << ' ' - << std::setfill(' ') << std::setw(9) << std::right << tpls_item.num_partial_tapes() << ' ' - << std::setfill(' ') << std::setw(12) << std::right << tpls_item.num_physical_files() << ' ' - << std::setfill(' ') << std::setw(4) << std::right << tpls_item.capacity_bytes() / 1000000000 << "G " - << std::setfill(' ') << std::setw(4) << std::right << tpls_item.data_bytes() / 1000000000 << "G " - << std::setfill(' ') << std::setw(5) << std::right << avail / 1000000000 << "G " - << std::setfill(' ') << std::setw(5) << std::right << std::fixed << std::setprecision(1) << use_percent << "% " - << std::setfill(' ') << std::setw(8) << std::right << encrypt_str << ' ' - << std::setfill(' ') << std::setw(20) << std::right << tpls_item.supply() << ' ' - << std::setfill(' ') << std::setw(8) << std::right << tpls_item.created().username() << ' ' - << std::setfill(' ') << std::setw(25) << std::right << tpls_item.created().host() << ' ' - << std::setfill(' ') << std::setw(24) << std::right << timeToString(tpls_item.created().time()) << ' ' - << std::setfill(' ') << std::setw(8) << std::right << tpls_item.modified().username() << ' ' - << std::setfill(' ') << std::setw(25) << std::right << tpls_item.modified().host() << ' ' - << std::setfill(' ') << std::setw(24) << std::right << timeToString(tpls_item.modified().time()) << ' ' - << tpls_item.comment() - << std::endl; + uint64_t avail = tpls_item.capacity_bytes() > tpls_item.data_bytes() ? + tpls_item.capacity_bytes()-tpls_item.data_bytes() : 0; + + double use_percent = tpls_item.capacity_bytes() > 0 ? + (static_cast<double>(tpls_item.data_bytes())/static_cast<double>(tpls_item.capacity_bytes()))*100.0 : 0.0; + + push_back( + tpls_item.name(), + tpls_item.vo(), + tpls_item.num_tapes(), + tpls_item.num_partial_tapes(), + tpls_item.num_physical_files(), + dataSizeToStr(tpls_item.capacity_bytes()), + dataSizeToStr(tpls_item.data_bytes()), + dataSizeToStr(avail), + doubleToStr(use_percent, '%'), + tpls_item.encrypt(), + tpls_item.supply(), + tpls_item.created().username(), + tpls_item.created().host(), + timeToStr(tpls_item.created().time()), + tpls_item.modified().username(), + tpls_item.modified().host(), + timeToStr(tpls_item.modified().time()), + tpls_item.comment() + ); } }} diff --git a/cmdline/CtaAdminTextFormatter.hpp b/cmdline/CtaAdminTextFormatter.hpp index 3c3c36b3ef317b6233db91c512f08b7ca4d10181..402412876488f7d55d1b8dcd039a115aaa9df28d 100644 --- a/cmdline/CtaAdminTextFormatter.hpp +++ b/cmdline/CtaAdminTextFormatter.hpp @@ -24,46 +24,109 @@ namespace cta { namespace admin { -class CtaAdminTextFormatter +class TextFormatter { public: - // Output headers - static void printAfLsHeader(); - static void printAfLsSummaryHeader(); - static void printFrLsHeader(); - static void printFrLsSummaryHeader(); - static void printLpaHeader(); - static void printLpaSummaryHeader(); - static void printLprHeader(); - static void printLprSummaryHeader(); - static void printTpLsHeader(); - static void printTapeLsHeader(); - static void printRepackLsHeader(); + /*! + * Constructor + * + * @param[in] bufLines Number of text lines to buffer before flushing formatted output + * (Not used for JSON output which does not need to be formatted + * so can be streamed directly) + */ + TextFormatter(unsigned int bufLines = 1000) : + m_bufLines(bufLines) { + m_outputBuffer.reserve(bufLines); + } + + ~TextFormatter() { + flush(); + } + + // Output headers + void printAfLsHeader(); + void printAfLsSummaryHeader(); + void printFrLsHeader(); + void printFrLsSummaryHeader(); + void printLpaHeader(); + void printLpaSummaryHeader(); + void printLprHeader(); + void printLprSummaryHeader(); + void printTapePoolLsHeader(); + void printTapeLsHeader(); + void printRepackLsHeader(); - // Output records - static void print(const ArchiveFileLsItem &afls_item); - static void print(const ArchiveFileLsSummary &afls_summary); - static void print(const FailedRequestLsItem &frls_item); - static void print(const FailedRequestLsSummary &frls_summary); - static void print(const ListPendingArchivesItem &lpa_item); - static void print(const ListPendingArchivesSummary &lpa_summary); - static void print(const ListPendingRetrievesItem &lpr_item); - static void print(const ListPendingRetrievesSummary &lpr_summary); - static void print(const TapePoolLsItem &tpls_item); - static void print(const TapeLsItem &tals_item); - static void print(const RepackLsItem &rels_item); + // Output records + void print(const ArchiveFileLsItem &afls_item); + void print(const ArchiveFileLsSummary &afls_summary); + void print(const FailedRequestLsItem &frls_item); + void print(const FailedRequestLsSummary &frls_summary); + void print(const ListPendingArchivesItem &lpa_item); + void print(const ListPendingArchivesSummary &lpa_summary); + void print(const ListPendingRetrievesItem &lpr_item); + void print(const ListPendingRetrievesSummary &lpr_summary); + void print(const TapePoolLsItem &tpls_item); + void print(const TapeLsItem &tals_item); + void print(const RepackLsItem &rels_item); private: - // Static method to convert time to string - static std::string timeToString(const time_t &time) - { - std::string timeString(ctime(&time)); - timeString.resize(timeString.size()-1); //remove newline - return timeString; - } - - static constexpr const char* const TEXT_RED = "\x1b[31;1m"; //!< Terminal formatting code for red text - static constexpr const char* const TEXT_NORMAL = "\x1b[0m"; //!< Terminal formatting code for normal text + //! Add a line to the buffer + template<typename... Args> + void push_back(Args... args) { + std::vector<std::string> line; + buildVector(line, args...); + m_outputBuffer.push_back(line); + if(m_outputBuffer.size() >= m_bufLines) flush(); + } + + //! Recursive variadic method to build a log string from an arbitrary number of items of arbitrary type + template<typename T, typename... Args> + static void buildVector(std::vector<std::string> &line, const T &item, Args... args) { + buildVector(line, item); + buildVector(line, args...); + } + + //! Base case method to add one item to the log + static void buildVector(std::vector<std::string> &line, const std::string &item) { + line.push_back(item); + } + + //! Base case method to add one item to the log, overloaded for char* + static void buildVector(std::vector<std::string> &line, const char *item) { + line.push_back(std::string(item)); + } + + //! Base case method to add one item to the log, overloaded for bool + static void buildVector(std::vector<std::string> &line, bool item) { + line.push_back(item ? "true" : "false"); + } + + /*! + * Base case method to add one item to the log, with partial specialisation + * (works for all integer and floating-point types) + */ + template<typename T> + static void buildVector(std::vector<std::string> &line, const T &item) { + line.push_back(std::to_string(item)); + } + + //! Convert double to string with one decimal place precision and a suffix + static std::string doubleToStr(double value, char unit); + + //! Convert UNIX time to string + static std::string timeToStr(const time_t &unixtime); + + //! Convert data size in bytes to abbreviated string with appropriate size suffix (K/M/G/T/P/E) + static std::string dataSizeToStr(uint64_t value); + + //! Flush buffer to stdout + void flush(); + + unsigned int m_bufLines; //!< Number of text lines to buffer before flushing formatted output + std::vector<std::vector<std::string>> m_outputBuffer; //!< Buffer for text output (not used for JSON) + + static constexpr const char* const TEXT_RED = "\x1b[31;1m"; //!< Terminal formatting code for red text + static constexpr const char* const TEXT_NORMAL = "\x1b[0m"; //!< Terminal formatting code for normal text }; }} diff --git a/continuousintegration/orchestration/tests/repack_systemtest.sh b/continuousintegration/orchestration/tests/repack_systemtest.sh index f6a911190cd1fa28ef017e4afbfa4f43639d2b68..f79f83e3018a5a33be953d45cbbf05fea31a54ac 100755 --- a/continuousintegration/orchestration/tests/repack_systemtest.sh +++ b/continuousintegration/orchestration/tests/repack_systemtest.sh @@ -89,7 +89,7 @@ echo "Launching repack request for VID ${VID_TO_REPACK}, bufferURL = ${FULL_REPA admin_cta re add --vid ${VID_TO_REPACK} --justmove --bufferurl ${FULL_REPACK_BUFFER_URL} SECONDS_PASSED=0 -while test 0 = `admin_cta repack ls --vid ${VID_TO_REPACK} | grep -E "Complete|Failed" | wc -l`; do +while test 0 = `admin_cta --json repack ls --vid ${VID_TO_REPACK} | jq -r '.[0] | select(.status == "Complete" or .status == "Failed")' | wc -l`; do echo "Waiting for repack request on tape ${VID_TO_REPACK} to be complete: Seconds passed = $SECONDS_PASSED" sleep 1 let SECONDS_PASSED=SECONDS_PASSED+1 @@ -99,7 +99,7 @@ while test 0 = `admin_cta repack ls --vid ${VID_TO_REPACK} | grep -E "Complete|F exit 1 fi done -if test 1 = `admin_cta repack ls --vid ${VID_TO_REPACK} | grep -E "Failed" | wc -l`; then +if test 1 = `admin_cta --json repack ls --vid ${VID_TO_REPACK} | jq -r '.[0] | select(.status == "Failed")' | wc -l`; then echo "Repack failed for tape ${VID_TO_REPACK}." exit 1 fi