CtaAdminCmd.cpp 21 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*!
 * @project        The CERN Tape Archive (CTA)
 * @brief          Command-line tool for CTA Admin commands
 * @description    CTA Admin command using Google Protocol Buffers and XRootD SSI transport
 * @copyright      Copyright 2017 CERN
 * @license        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/>.
 */

20
21
#include <sstream>
#include <iostream>
22

23
#include <XrdSsiPbLog.hpp>
24
25
#include <XrdSsiPbIStreamBuffer.hpp>

26
27
#include <cmdline/CtaAdminCmd.hpp>
#include <cmdline/CtaAdminTextFormatter.hpp>
28

29

30
31
32
// GLOBAL VARIABLES : used to pass information between main thread and stream handler thread

// global synchronisation flag
33
std::atomic<bool> isHeaderSent(false);
34

35
36
// initialise an output buffer of 1000 lines
cta::admin::TextFormatter formattedText(1000);
37

38

39
40
namespace XrdSsiPb {

41
42
43
44
45
46
47
48
49
50
/*!
 * User error exception
 */
class UserException : public std::runtime_error
{
public:
  UserException(const std::string &err_msg) : std::runtime_error(err_msg) {}
};


51
52
53
54
55
56
57
58
59
/*!
 * Alert callback.
 *
 * Defines how Alert messages should be logged
 */
template<>
void RequestCallback<cta::xrd::Alert>::operator()(const cta::xrd::Alert &alert)
{
   std::cout << "AlertCallback():" << std::endl;
60
   Log::DumpProtobuf(Log::PROTOBUF, &alert);
61
62
}

63
64
65
66
67
68
69

/*!
 * Data/Stream callback.
 *
 * Defines how incoming records from the stream should be handled
 */
template<>
70
void IStreamBuffer<cta::xrd::Data>::DataCallback(cta::xrd::Data record) const
71
{
72
73
   using namespace cta::xrd;
   using namespace cta::admin;
74

75
76
77
   // Wait for primary response to be handled before allowing stream response
   while(!isHeaderSent) { std::this_thread::yield(); }

Michael Davis's avatar
Michael Davis committed
78
79
80
   // Output results in JSON format for parsing by a script
   if(CtaAdminCmd::isJson())
   {
81
82
      std::cout << CtaAdminCmd::jsonDelim();

Michael Davis's avatar
Michael Davis committed
83
      switch(record.data_case()) {
84
         case Data::kAdlsItem:      std::cout << Log::DumpProtobuf(&record.adls_item());    break;
85
86
         case Data::kAflsItem:      std::cout << Log::DumpProtobuf(&record.afls_item());    break;
         case Data::kAflsSummary:   std::cout << Log::DumpProtobuf(&record.afls_summary()); break;
87
         case Data::kArlsItem:      std::cout << Log::DumpProtobuf(&record.arls_item());    break;
88
         case Data::kDrlsItem:      std::cout << Log::DumpProtobuf(&record.drls_item());    break;
89
90
         case Data::kFrlsItem:      std::cout << Log::DumpProtobuf(&record.frls_item());    break;
         case Data::kFrlsSummary:   std::cout << Log::DumpProtobuf(&record.frls_summary()); break;
91
         case Data::kGmrlsItem:     std::cout << Log::DumpProtobuf(&record.gmrls_item());   break;
92
93
94
95
         case Data::kLpaItem:       std::cout << Log::DumpProtobuf(&record.lpa_item());     break;
         case Data::kLpaSummary:    std::cout << Log::DumpProtobuf(&record.lpa_summary());  break;
         case Data::kLprItem:       std::cout << Log::DumpProtobuf(&record.lpr_item());     break;
         case Data::kLprSummary:    std::cout << Log::DumpProtobuf(&record.lpr_summary());  break;
96
97
         case Data::kLllsItem:      std::cout << Log::DumpProtobuf(&record.llls_item());    break;
         case Data::kMplsItem:      std::cout << Log::DumpProtobuf(&record.mpls_item());    break;
98
         case Data::kRelsItem:      std::cout << Log::DumpProtobuf(&record.rels_item());    break;
99
         case Data::kRmrlsItem:     std::cout << Log::DumpProtobuf(&record.rmrls_item());   break;
100
101
         case Data::kSqItem:        std::cout << Log::DumpProtobuf(&record.sq_item());      break;
         case Data::kSclsItem:      std::cout << Log::DumpProtobuf(&record.scls_item());    break;
102
         case Data::kTalsItem:      std::cout << Log::DumpProtobuf(&record.tals_item());    break;
103
         case Data::kTflsItem:      std::cout << Log::DumpProtobuf(&record.tfls_item());    break;
104
         case Data::kTplsItem:      std::cout << Log::DumpProtobuf(&record.tpls_item());    break;
105
         case Data::kDslsItem:      std::cout << Log::DumpProtobuf(&record.dsls_item());    break;
106
107
         case Data::kVolsItem:      std::cout << Log::DumpProtobuf(&record.vols_item());    break;
         case Data::kVersionItem:   std::cout << Log::DumpProtobuf(&record.version_item()); break;
108
         case Data::kMtlsItem:      std::cout << Log::DumpProtobuf(&record.mtls_item());    break;
Michael Davis's avatar
Michael Davis committed
109
110
111
112
113
         default:
            throw std::runtime_error("Received invalid stream data from CTA Frontend.");
      }
   }
   // Format results in a tabular format for a human
114
   else switch(record.data_case()) {
115
116
         case Data::kAdlsItem:      formattedText.print(record.adls_item());    break;
         case Data::kAflsItem:      formattedText.print(record.afls_item());    break;
117
         case Data::kAflsSummary:   formattedText.print(record.afls_summary()); break;
118
         case Data::kArlsItem:      formattedText.print(record.arls_item());    break;
119
         case Data::kDrlsItem:      formattedText.print(record.drls_item());    break;
120
121
         case Data::kFrlsItem:      formattedText.print(record.frls_item());    break;
         case Data::kFrlsSummary:   formattedText.print(record.frls_summary()); break;
122
         case Data::kGmrlsItem:     formattedText.print(record.gmrls_item());   break;
123
124
125
126
         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;
127
128
         case Data::kLllsItem:      formattedText.print(record.llls_item());    break;
         case Data::kMplsItem:      formattedText.print(record.mpls_item());    break;
129
         case Data::kRelsItem:      formattedText.print(record.rels_item());    break;
130
         case Data::kRmrlsItem:     formattedText.print(record.rmrls_item());   break;
131
132
         case Data::kSqItem:        formattedText.print(record.sq_item());      break;
         case Data::kSclsItem:      formattedText.print(record.scls_item());    break;
133
         case Data::kTalsItem:      formattedText.print(record.tals_item());    break;
134
         case Data::kTflsItem:      formattedText.print(record.tfls_item());    break;
135
         case Data::kTplsItem:      formattedText.print(record.tpls_item());    break;
136
         case Data::kDslsItem:      formattedText.print(record.dsls_item());    break;
137
         case Data::kVolsItem:      formattedText.print(record.vols_item());    break;
138
         case Data::kVersionItem:   formattedText.print(record.version_item()); break;
139
         case Data::kMtlsItem:      formattedText.print(record.mtls_item());    break;
140
         default:
141
            throw std::runtime_error("Received invalid stream data from CTA Frontend.");
142
   }
143
144
}

145
146
147
148
} // namespace XrdSsiPb



149
150
namespace cta {
namespace admin {
151

152
153
std::atomic<bool> CtaAdminCmd::is_json(false);
std::atomic<bool> CtaAdminCmd::is_first_record(true);
Michael Davis's avatar
Michael Davis committed
154

155
156
CtaAdminCmd::CtaAdminCmd(int argc, const char *const *const argv) :
   m_execname(argv[0])
157
{
158
   auto &admincmd = *(m_request.mutable_admincmd());
159
160
161
162
   
   m_request.set_client_cta_version(CTA_VERSION);
   m_request.set_client_xrootd_ssi_protobuf_interface_version(XROOTD_SSI_PROTOBUF_INTERFACE_VERSION);
   
163
   // Strip path from execname
164

165
166
   size_t p = m_execname.find_last_of('/');
   if(p != std::string::npos) m_execname.erase(0, p+1);
167

168
   // Parse the command
169

170
   cmdLookup_t::const_iterator cmd_it;
171

Michael Davis's avatar
Michael Davis committed
172
173
174
175
176
177
178
179
180
181
182
   // Client-side only options

   int argno = 1;

   if(argc <= argno) throwUsage();

   if(std::string(argv[argno]) == "--json") { is_json = true; ++argno; }

   // Commands, subcommands and server-side options

   if(argc <= argno || (cmd_it = cmdLookup.find(argv[argno++])) == cmdLookup.end()) {
183
184
185
      throwUsage();
   } else {
      admincmd.set_cmd(cmd_it->second);
186
   }
187

188
189
   // Help is a special subcommand which suppresses errors and prints usage
   
Michael Davis's avatar
Michael Davis committed
190
   if(argc > argno && std::string(argv[argno]) == "help") {
191
192
193
      throwUsage();
   }

194
195
   // Parse the subcommand

196
197
198
   bool has_subcommand = cmdHelp.at(admincmd.cmd()).has_subcommand();

   if(has_subcommand)
199
   {
200
      subcmdLookup_t::const_iterator subcmd_it;
201

Michael Davis's avatar
Michael Davis committed
202
      if(argc <= argno) {
203
         throwUsage("Missing subcommand");
Michael Davis's avatar
Michael Davis committed
204
205
      } else if((subcmd_it = subcmdLookup.find(argv[argno])) == subcmdLookup.end()) {
         throwUsage(std::string("Invalid subcommand: ") + argv[argno]);
206
207
      } else {
         admincmd.set_subcmd(subcmd_it->second);
208
      }
209
   }
210

211
212
213
214
215
   // Parse the options

   auto option_list_it = cmdOptions.find(cmd_key_t{ admincmd.cmd(), admincmd.subcmd() });

   if(option_list_it == cmdOptions.end()) {
Michael Davis's avatar
Michael Davis committed
216
      throwUsage(std::string("Invalid subcommand: ") + argv[argno]);
217
218
   }

Michael Davis's avatar
Michael Davis committed
219
   parseOptions(has_subcommand ? argno+1 : argno, argc, argv, option_list_it->second);
220
221
222
223
224
225
226
}



void CtaAdminCmd::send() const
{
   // Validate the Protocol Buffer
227
   try {
228
      validateCmd(m_request.admincmd());
229
230
231
   } catch(std::runtime_error &ex) {
      throwUsage(ex.what());
   }
232
   
Michael Davis's avatar
Michael Davis committed
233
   // Set configuration options
234
235
   const std::string config_file = "/etc/cta/cta-cli.conf";
   XrdSsiPb::Config config(config_file, "cta");
236
   config.set("resource", "/ctafrontend");
237
238
   config.set("response_bufsize", StreamBufferSize);         // default value = 1024 bytes
   config.set("request_timeout", DefaultRequestTimeout);     // default value = 10s
239
240
241

   // Allow environment variables to override config file
   config.getEnv("request_timeout", "XRD_REQUESTTIMEOUT");
242
243
244
245
246
247
248

   // If XRDDEBUG=1, switch on all logging
   if(getenv("XRDDEBUG")) {
      config.set("log", "all");
   }
   // If fine-grained control over log level is required, use XrdSsiPbLogLevel
   config.getEnv("log", "XrdSsiPbLogLevel");
249

250
251
252
253
254
   // Validate that endpoint was specified in the config file
   if(!config.getOptionValueStr("endpoint").first) {
      throw std::runtime_error("Configuration error: cta.endpoint missing from " + config_file);
   }

255
256
257
   // If the server is down, we want an immediate failure. Set client retry to a single attempt.
   XrdSsiProviderClient->SetTimeout(XrdSsiProvider::connect_N, 1);

Michael Davis's avatar
Michael Davis committed
258
   // Obtain a Service Provider
259
   XrdSsiPbServiceType cta_service(config);
260
261

   // Send the Request to the Service and get a Response
262
   cta::xrd::Response response;
263
   auto stream_future = cta_service.SendAsync(m_request, response);
264
265
266
267
268

   // Handle responses
   switch(response.type())
   {
      using namespace cta::xrd;
269
270
271
272
273
274
      using namespace cta::admin;

      case Response::RSP_SUCCESS:
         // Print message text
         std::cout << response.message_txt();
         // Print streaming response header
275
         if(!isJson()) switch(response.show_header()) {
276
277
278
279
280
281
282
            case HeaderType::ADMIN_LS:                     formattedText.printAdminLsHeader(); break;
            case HeaderType::ARCHIVEFILE_LS:               formattedText.printArchiveFileLsHeader(); break;
            case HeaderType::ARCHIVEFILE_LS_SUMMARY:       formattedText.printArchiveFileLsSummaryHeader(); break;
            case HeaderType::ARCHIVEROUTE_LS:              formattedText.printArchiveRouteLsHeader(); break;
            case HeaderType::DRIVE_LS:                     formattedText.printDriveLsHeader(); break;
            case HeaderType::FAILEDREQUEST_LS:             formattedText.printFailedRequestLsHeader(); break;
            case HeaderType::FAILEDREQUEST_LS_SUMMARY:     formattedText.printFailedRequestLsSummaryHeader(); break;
283
            case HeaderType::GROUPMOUNTRULE_LS:            formattedText.printGroupMountRuleLsHeader(); break;
284
285
286
287
            case HeaderType::LISTPENDINGARCHIVES:          formattedText.printListPendingArchivesHeader(); break;
            case HeaderType::LISTPENDINGARCHIVES_SUMMARY:  formattedText.printListPendingArchivesSummaryHeader(); break;
            case HeaderType::LISTPENDINGRETRIEVES:         formattedText.printListPendingRetrievesHeader(); break;
            case HeaderType::LISTPENDINGRETRIEVES_SUMMARY: formattedText.printListPendingRetrievesSummaryHeader(); break;
288
289
            case HeaderType::LOGICALLIBRARY_LS:            formattedText.printLogicalLibraryLsHeader(); break;
            case HeaderType::MOUNTPOLICY_LS:               formattedText.printMountPolicyLsHeader(); break;
290
            case HeaderType::REPACK_LS:                    formattedText.printRepackLsHeader(); break;
291
            case HeaderType::REQUESTERMOUNTRULE_LS:        formattedText.printRequesterMountRuleLsHeader(); break;
292
293
            case HeaderType::SHOWQUEUES:                   formattedText.printShowQueuesHeader(); break;
            case HeaderType::STORAGECLASS_LS:              formattedText.printStorageClassLsHeader(); break;
294
            case HeaderType::TAPE_LS:                      formattedText.printTapeLsHeader(); break;
295
            case HeaderType::TAPEFILE_LS:                  formattedText.printTapeFileLsHeader(); break;
296
            case HeaderType::TAPEPOOL_LS:                  formattedText.printTapePoolLsHeader(); break;
297
            case HeaderType::DISKSYSTEM_LS:                formattedText.printDiskSystemLsHeader(); break;
298
            case HeaderType::VIRTUALORGANIZATION_LS:       formattedText.printVirtualOrganizationLsHeader(); break;
Michael Davis's avatar
Michael Davis committed
299
            case HeaderType::VERSION_CMD:                  formattedText.printVersionHeader(); break;
300
            case HeaderType::NONE:
301
            default:                                       break;
302
         }
303
304
         // Allow stream processing to commence
         isHeaderSent = true;
305
         break;
306
      case Response::RSP_ERR_PROTOBUF:                     throw XrdSsiPb::PbException(response.message_txt());
307
      case Response::RSP_ERR_USER:                         throw XrdSsiPb::UserException(response.message_txt());
308
309
      case Response::RSP_ERR_CTA:                          throw std::runtime_error(response.message_txt());
      default:                                             throw XrdSsiPb::PbException("Invalid response type.");
310
   }
311

312
313
   // If there is a Data/Stream payload, wait until it has been processed before exiting
   stream_future.wait();
314
315

   // JSON output is an array of structs, close bracket
316
   if(isJson()) { std::cout << jsonCloseDelim(); }
317
318
319
}


320

321
322
323
void CtaAdminCmd::parseOptions(int start, int argc, const char *const *const argv, const cmd_val_t &options)
{
   for(int i = start; i < argc; ++i)
324
   {
325
326
      int opt_num = i-start;

327
328
329
      cmd_val_t::const_iterator opt_it;

      // Scan options for a match
330

331
      for(opt_it = options.begin(); opt_it != options.end(); ++opt_it) {
332
         // Special case of OPT_CMD type has an implicit key
333
         if(opt_num-- == 0 && opt_it->get_type() == Option::OPT_CMD) break;
334

335
336
         if(*opt_it == argv[i]) break;
      }
337
338
339
340
      if(opt_it == options.end()) {
         throwUsage(std::string("Invalid option: ") + argv[i]);
      }
      if((i += opt_it->num_params()) == argc) {
341
         throw std::runtime_error(std::string(argv[i-1]) + " expects a parameter: " + opt_it->help());
342
      }
343

344
      addOption(*opt_it, argv[i]);
345
   }
346
347
348
}


349

350
void CtaAdminCmd::addOption(const Option &option, const std::string &value)
351
{
352
353
354
355
356
357
358
359
360
361
362
363
   auto admincmd_ptr = m_request.mutable_admincmd();

   switch(option.get_type())
   {
      case Option::OPT_CMD:
      case Option::OPT_STR: {
         auto key = strOptions.at(option.get_key());
         auto new_opt = admincmd_ptr->add_option_str();
         new_opt->set_key(key);
         new_opt->set_value(value);
         break;
      }
364
365
366
367
368
369
370
      case Option::OPT_STR_LIST: {
         auto key = strListOptions.at(option.get_key());
         auto new_opt = admincmd_ptr->add_option_str_list();
         new_opt->set_key(key);
         readListFromFile(*new_opt, value);
         break;
      }
371
372
373
374
375
376
377
378
379
380
381
382
383
384
      case Option::OPT_FLAG:
      case Option::OPT_BOOL: {
         auto key = boolOptions.at(option.get_key());
         auto new_opt = admincmd_ptr->add_option_bool();
         new_opt->set_key(key);
         if(option.get_type() == Option::OPT_FLAG || value == "true") {
            new_opt->set_value(true);
         } else if(value == "false") {
            new_opt->set_value(false);
         } else {
            throw std::runtime_error(value + " is not a boolean value: " + option.help());
         }
         break;
      }
385
      case Option::OPT_UINT: try {
386
         auto key = uint64Options.at(option.get_key());
387
388
         int64_t val_int = std::stol(value);
         if(val_int < 0) throw std::out_of_range("value is negative");
389
390
         auto new_opt = admincmd_ptr->add_option_uint64();
         new_opt->set_key(key);
391
         new_opt->set_value(val_int);
392
         break;
393
394
395
396
      } catch(std::invalid_argument &) {
         throw std::runtime_error(value + " is not a valid uint64: " + option.help());
      } catch(std::out_of_range &) {
         throw std::runtime_error(value + " is out of range: " + option.help());
397
398
      }
   }
399
400
401
402
}



403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
void CtaAdminCmd::readListFromFile(cta::admin::OptionStrList &str_list, const std::string &filename)
{
   std::ifstream file(filename);
   if (file.fail()) {
      throw std::runtime_error("Unable to open file " + filename);
   }

   std::string line;

   while(std::getline(file, line)) {
      // Strip out comments
      auto pos = line.find('#');
      if(pos != std::string::npos) {
         line.resize(pos);
      }

      // Extract the list items
      std::stringstream ss(line);
      while(!ss.eof()) {
         std::string item;
         ss >> item;
424
425
426
427
428
429
         // skip blank lines or lines consisting only of whitespace
         if(item.empty()) continue;

         if(str_list.key() == OptionStrList::FILE_ID) {
            // Special handling for file id lists. The output from "eos find --fid <fid> /path" is:
            //   path=/path fid=<fid>
430
431
            // We discard everything except the list of fids. <fid> is a zero-padded hexadecimal number,
            // but in the CTA catalogue we store disk IDs as a decimal string, so we need to convert it.
432
            if(item.substr(0, 4) == "fid=") {
433
434
435
436
437
               auto fid = strtol(item.substr(4).c_str(), nullptr, 16);
               if(fid < 1 || fid == LONG_MAX) {
                 throw std::runtime_error(item + " is not a valid file ID");
               }
               str_list.add_item(std::to_string(fid));
438
439
440
441
442
            } else {
               continue;
            }
         } else {
            // default case: add all items
443
444
445
446
447
448
449
450
            str_list.add_item(item);
         }
      }
   }
}



451
void CtaAdminCmd::throwUsage(const std::string &error_txt) const
452
{
453
   std::stringstream help;
454
   const auto &admincmd = m_request.admincmd().cmd();
455

456
457
458
459
   if(error_txt != "") {
      help << error_txt << std::endl;
   }

460
   if(admincmd == AdminCmd::CMD_NONE)
461
   {
462
      // Command has not been set: show generic help
463

464
465
466
467
      help << "CTA Admin commands:"                                                          << std::endl << std::endl
           << "For each command there is a short version and a long one. Subcommands (add/ch/ls/rm/etc.)" << std::endl
           << "do not have short versions. For detailed help on the options of each subcommand, type:"    << std::endl
           << "  " << m_execname << " <command> help"                                        << std::endl << std::endl;
468

469
      for(auto cmd_it = cmdHelp.begin(); cmd_it != cmdHelp.end(); ++cmd_it)
470
      {
471
         help << "  " << m_execname << ' ' << cmd_it->second.short_help() << std::endl;
472
      }
473
474
475
   }
   else
   {
476
      // Command has been set: show command-specific help
477

478
      help << m_execname << ' ' << cmdHelp.at(admincmd).help();
479
   }
480

481
   throw std::runtime_error(help.str());
482
483
}

484
}} // namespace cta::admin
485
486
487
488



/*!
489
490
491
492
 * Start here
 *
 * @param    argc[in]    The number of command-line arguments
 * @param    argv[in]    The command-line arguments
493
494
 */

495
496
497
498
499
int main(int argc, const char **argv)
{
   using namespace cta::admin;

   try {    
500
      // Parse the command line arguments
501
      CtaAdminCmd cmd(argc, argv);
502
503
504
505

      // Send the protocol buffer
      cmd.send();

506
507
508
      // Delete all global objects allocated by libprotobuf
      google::protobuf::ShutdownProtobufLibrary();

509
510
511
512
513
      return 0;
   } catch (XrdSsiPb::PbException &ex) {
      std::cerr << "Error in Google Protocol Buffers: " << ex.what() << std::endl;
   } catch (XrdSsiPb::XrdSsiException &ex) {
      std::cerr << "Error from XRootD SSI Framework: " << ex.what() << std::endl;
514
515
516
517
   } catch (XrdSsiPb::UserException &ex) {
      if(CtaAdminCmd::isJson()) std::cout << CtaAdminCmd::jsonCloseDelim();
      std::cerr << ex.what() << std::endl;
      return 2;
518
519
520
521
522
523
524
   } catch (std::runtime_error &ex) {
      std::cerr << ex.what() << std::endl;
   } catch (std::exception &ex) {
      std::cerr << "Caught exception: " << ex.what() << std::endl;
   } catch (...) {
      std::cerr << "Caught an unknown exception" << std::endl;
   }
525

526
   return 1;
527
}