2 sfi.py - basic SFA command-line client
3 this module is also used in sfascan
6 # pylint: disable=c0111, c0413
8 from __future__ import print_function
21 from lxml import etree
22 from optparse import OptionParser
23 from pprint import PrettyPrinter
24 from tempfile import mkstemp
26 from sfa.trust.certificate import Keypair, Certificate
27 from sfa.trust.gid import GID
28 from sfa.trust.credential import Credential
29 from sfa.trust.sfaticket import SfaTicket
31 from sfa.util.faults import SfaInvalidArgument
32 from sfa.util.sfalogging import init_logger, logger
33 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
34 from sfa.util.config import Config
35 from sfa.util.version import version_core
36 from sfa.util.cache import Cache
37 from sfa.util.printable import printable
38 from sfa.util.py23 import StringIO
40 from sfa.storage.record import Record
42 from sfa.rspecs.rspec import RSpec
43 from sfa.rspecs.rspec_converter import RSpecConverter
44 from sfa.rspecs.version_manager import VersionManager
46 from sfa.client.sfaclientlib import SfaClientBootstrap
47 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
48 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
49 from sfa.client.return_value import ReturnValue
50 from sfa.client.candidates import Candidates
51 from sfa.client.manifolduploader import ManifoldUploader
54 DEFAULT_RSPEC_VERSION = "GENI 3"
56 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
57 terminal_render, filter_records
62 def display_rspec(rspec, format='rspec'):
64 tree = etree.parse(StringIO(rspec))
66 result = root.xpath("./network/site/node/hostname/text()")
67 elif format in ['ip']:
68 # The IP address is not yet part of the new RSpec
69 # so this doesn't do anything yet.
70 tree = etree.parse(StringIO(rspec))
72 result = root.xpath("./network/site/node/ipv4/text()")
80 def display_list(results):
81 for result in results:
85 def display_records(recordList, dump=False):
86 ''' Print all fields in the record'''
87 for record in recordList:
88 display_record(record, dump)
91 def display_record(record, dump=False):
93 record.dump(sort=True)
95 info = record.getdict()
96 print("{} ({})".format(info['hrn'], info['type']))
100 def filter_records(type, records):
101 filtered_records = []
102 for record in records:
103 if (record['type'] == type) or (type == "all"):
104 filtered_records.append(record)
105 return filtered_records
108 def credential_printable(cred):
109 credential = Credential(cred=cred)
111 result += credential.pretty_cred()
113 rights = credential.get_privileges()
114 result += "type={}\n".format(credential.type)
115 result += "version={}\n".format(credential.version)
116 result += "rights={}\n".format(rights)
120 def show_credentials(cred_s):
121 if not isinstance(cred_s, list):
124 print("Using Credential {}".format(credential_printable(cred)))
131 def save_raw_to_file(var, filename, format='text', banner=None):
133 _save_raw_to_file(var, sys.stdout, format, banner)
135 with open(filename, w) as fileobj:
136 _save_raw_to_file(var, fileobj, format, banner)
137 print("(Over)wrote {}".format(filename))
140 def _save_raw_to_file(var, f, format, banner):
143 f.write(banner + "\n")
144 f.write("{}".format(var))
146 f.write('\n' + banner + "\n")
147 elif format == "pickled":
148 f.write(pickle.dumps(var))
149 elif format == "json":
150 f.write(json.dumps(var)) # python 2.6
152 # this should never happen
153 print("unknown output format", format)
158 def save_rspec_to_file(rspec, filename):
159 if not filename.endswith(".rspec"):
160 filename = filename + ".rspec"
161 with open(filename, 'w') as f:
162 f.write("{}".format(rspec))
163 print("(Over)wrote {}".format(filename))
166 def save_record_to_file(filename, record_dict):
167 record = Record(dict=record_dict)
168 xml = record.save_as_xml()
169 with codecs.open(filename, encoding='utf-8', mode="w") as f:
171 print("(Over)wrote {}".format(filename))
174 def save_records_to_file(filename, record_dicts, format="xml"):
176 for index, record_dict in enumerate(record_dicts):
177 save_record_to_file(filename + "." + str(index), record_dict)
178 elif format == "xmllist":
179 with open(filename, "w") as f:
180 f.write("<recordlist>\n")
181 for record_dict in record_dicts:
182 record_obj = Record(dict=record_dict)
183 f.write('<record hrn="' + record_obj.hrn +
184 '" type="' + record_obj.type + '" />\n')
185 f.write("</recordlist>\n")
186 print("(Over)wrote {}".format(filename))
188 elif format == "hrnlist":
189 with open(filename, "w") as f:
190 for record_dict in record_dicts:
191 record_obj = Record(dict=record_dict)
192 f.write(record_obj.hrn + "\n")
193 print("(Over)wrote {}".format(filename))
196 # this should never happen
197 print("unknown output format", format)
199 # minimally check a key argument
202 def check_ssh_key(key):
203 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
204 return re.match(good_ssh_key, key, re.IGNORECASE)
209 def normalize_type(type):
210 if type.startswith('au'):
212 elif type.startswith('us'):
214 elif type.startswith('sl'):
216 elif type.startswith('no'):
218 elif type.startswith('ag'):
220 elif type.startswith('al'):
223 print('unknown type {} - should start with one of au|us|sl|no|ag|al'.format(type))
227 def load_record_from_opts(options):
229 if hasattr(options, 'xrn') and options.xrn:
230 if hasattr(options, 'type') and options.type:
231 xrn = Xrn(options.xrn, options.type)
233 xrn = Xrn(options.xrn)
234 record_dict['urn'] = xrn.get_urn()
235 record_dict['hrn'] = xrn.get_hrn()
236 record_dict['type'] = xrn.get_type()
237 if hasattr(options, 'key') and options.key:
239 pubkey = open(options.key, 'r').read()
242 if not check_ssh_key(pubkey):
243 raise SfaInvalidArgument(
244 name='key', msg="Could not find file, or wrong key format")
245 record_dict['reg-keys'] = [pubkey]
246 if hasattr(options, 'slices') and options.slices:
247 record_dict['slices'] = options.slices
248 if hasattr(options, 'reg_researchers') and options.reg_researchers is not None:
249 record_dict['reg-researchers'] = options.reg_researchers
250 if hasattr(options, 'email') and options.email:
251 record_dict['email'] = options.email
252 # authorities can have a name for standalone deployment
253 if hasattr(options, 'name') and options.name:
254 record_dict['name'] = options.name
255 if hasattr(options, 'reg_pis') and options.reg_pis:
256 record_dict['reg-pis'] = options.reg_pis
258 # handle extra settings
259 record_dict.update(options.extras)
261 return Record(dict=record_dict)
264 def load_record_from_file(filename):
265 with codecs.open(filename, encoding="utf-8", mode="r") as f:
267 return Record(xml=xml_str)
272 def unique_call_id(): return uuid.uuid4().urn
274 # a simple model for maintaing 3 doc attributes per command (instead of just one)
275 # essentially for the methods that implement a subcommand like sfi list
276 # we need to keep track of
277 # (*) doc a few lines that tell what it does, still located in __doc__
278 # (*) args_string a simple one-liner that describes mandatory arguments
279 # (*) example well, one or several releant examples
281 # since __doc__ only accounts for one, we use this simple mechanism below
282 # however we keep doc in place for easier migration
284 from functools import wraps
286 # we use a list as well as a dict so we can keep track of the order
291 def declare_command(args_string, example, aliases=None):
293 name = getattr(m, '__name__')
294 doc = getattr(m, '__doc__', "-- missing doc --")
295 doc = doc.strip(" \t\n")
296 commands_list.append(name)
297 # last item is 'canonical' name, so we can know which commands are
299 command_tuple = (doc, args_string, example, name)
300 commands_dict[name] = command_tuple
301 if aliases is not None:
302 for alias in aliases:
303 commands_list.append(alias)
304 commands_dict[alias] = command_tuple
307 def new_method(*args, **kwds): return m(*args, **kwds)
312 def remove_none_fields(record):
313 none_fields = [k for (k, v) in record.items() if v is None]
314 for k in none_fields:
322 # dirty hack to make this class usable from the outside
323 required_options = ['verbose', 'debug', 'registry',
324 'sm', 'auth', 'user', 'user_private_key']
327 def default_sfi_dir():
328 if os.path.isfile("./sfi_config"):
331 return os.path.expanduser("~/.sfi/")
333 # dummy to meet Sfi's expectations for its 'options' field
334 # i.e. s/t we can do setattr on
338 def __init__(self, options=None):
340 options = Sfi.DummyOptions()
341 for opt in Sfi.required_options:
342 if not hasattr(options, opt):
343 setattr(options, opt, None)
344 if not hasattr(options, 'sfi_dir'):
345 options.sfi_dir = Sfi.default_sfi_dir()
346 self.options = options
348 self.authority = None
350 self.logger.enable_console()
351 # various auxiliary material that we keep at hand
353 # need to call this other than just 'config' as we have a
354 # command/method with that name
355 self.config_instance = None
356 self.config_file = None
357 self.client_bootstrap = None
359 # suitable if no reasonable command has been provided
360 def print_commands_help(self, options):
361 verbose = getattr(options, 'verbose')
362 format3 = "%10s %-35s %s"
366 print(format3 % ("command", "cmd_args", "description"))
370 self.create_parser_global().print_help()
371 # preserve order from the code
372 for command in commands_list:
374 (doc, args_string, example, canonical) = commands_dict[command]
376 print("Cannot find info on command %s - skipped" % command)
380 if command == canonical:
381 doc = doc.replace("\n", "\n" + format3offset * ' ')
382 print(format3 % (command, args_string, doc))
384 self.create_parser_command(command).print_help()
386 print(format3 % (command, "<<alias for %s>>" % canonical, ""))
388 # now if a known command was found we can be more verbose on that one
389 def print_help(self):
390 print("==================== Generic sfi usage")
391 self.sfi_parser.print_help()
392 (doc, _, example, canonical) = commands_dict[self.command]
393 if canonical != self.command:
394 print("\n==================== NOTE: {} is an alias for genuine {}"
395 .format(self.command, canonical))
396 self.command = canonical
397 print("\n==================== Purpose of {}".format(self.command))
399 print("\n==================== Specific usage for {}".format(self.command))
400 self.command_parser.print_help()
402 print("\n==================== {} example(s)".format(self.command))
405 def create_parser_global(self):
406 # Generate command line parser
407 parser = OptionParser(add_help_option=False,
408 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
409 description="Commands: {}".format(" ".join(commands_list)))
410 parser.add_option("-r", "--registry", dest="registry",
411 help="root registry", metavar="URL", default=None)
412 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
413 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
414 parser.add_option("-R", "--raw", dest="raw", default=None,
415 help="Save raw, unparsed server response to a file")
416 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
417 help="raw file format ([text]|pickled|json)", default="text",
418 choices=("text", "pickled", "json"))
419 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
420 help="text string to write before and after raw output")
421 parser.add_option("-d", "--dir", dest="sfi_dir",
422 help="config & working directory - default is %default",
423 metavar="PATH", default=Sfi.default_sfi_dir())
424 parser.add_option("-u", "--user", dest="user",
425 help="user name", metavar="HRN", default=None)
426 parser.add_option("-a", "--auth", dest="auth",
427 help="authority name", metavar="HRN", default=None)
428 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
429 help="verbose mode - cumulative")
430 parser.add_option("-D", "--debug",
431 action="store_true", dest="debug", default=False,
432 help="Debug (xml-rpc) protocol messages")
433 # would it make sense to use ~/.ssh/id_rsa as a default here ?
434 parser.add_option("-k", "--private-key",
435 action="store", dest="user_private_key", default=None,
436 help="point to the private key file to use if not yet installed in sfi_dir")
437 parser.add_option("-t", "--timeout", dest="timeout", default=None,
438 help="Amout of time to wait before timing out the request")
439 parser.add_option("-h", "--help",
440 action="store_true", dest="help", default=False,
441 help="one page summary on commands & exit")
442 parser.disable_interspersed_args()
446 def create_parser_command(self, command):
447 if command not in commands_dict:
448 msg = "Invalid command\n"
450 msg += ','.join(commands_list)
451 self.logger.critical(msg)
454 # retrieve args_string
455 (_, args_string, __, canonical) = commands_dict[command]
457 parser = OptionParser(add_help_option=False,
458 usage="sfi [sfi_options] {} [cmd_options] {}"
459 .format(command, args_string))
460 parser.add_option("-h", "--help", dest='help', action='store_true', default=False,
461 help="Summary of one command usage")
463 if canonical in ("config"):
464 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
465 help='how myslice config variables as well')
467 if canonical in ("version"):
468 parser.add_option("-l", "--local",
469 action="store_true", dest="version_local", default=False,
470 help="display version of the local client")
472 if canonical in ("version", "trusted", "introspect"):
473 parser.add_option("-R", "--registry_interface",
474 action="store_true", dest="registry_interface", default=False,
475 help="target the registry interface instead of slice interface")
477 if canonical in ("register", "update"):
478 parser.add_option('-x', '--xrn', dest='xrn',
479 metavar='<xrn>', help='object hrn/urn (mandatory)')
480 parser.add_option('-t', '--type', dest='type', metavar='<type>',
481 help='object type (2 first chars is enough)', default=None)
482 parser.add_option('-e', '--email', dest='email',
483 default="", help="email (mandatory for users)")
484 parser.add_option('-n', '--name', dest='name',
485 default="", help="name (optional for authorities)")
486 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
488 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
489 default='', type="str", action='callback', callback=optparse_listvalue_callback)
490 parser.add_option('-r', '--researchers', dest='reg_researchers', metavar='<researchers>',
491 help='Set/replace slice researchers - use -r none to reset', default=None, type="str", action='callback',
492 callback=optparse_listvalue_callback)
493 parser.add_option('-p', '--pis', dest='reg_pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
494 default='', type="str", action='callback', callback=optparse_listvalue_callback)
495 parser.add_option('-X', '--extra', dest='extras', default={}, type='str', metavar="<EXTRA_ASSIGNS>",
496 action="callback", callback=optparse_dictvalue_callback, nargs=1,
497 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
499 # user specifies remote aggregate/sm/component
500 if canonical in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
501 "action", "shutdown", "renew", "status"):
502 parser.add_option("-d", "--delegate", dest="delegate", default=None,
504 help="Include a credential delegated to the user's root" +
505 "authority in set of credentials for this call")
507 # show_credential option
508 if canonical in ("list", "resources", "describe", "provision", "allocate", "register",
509 "update", "remove", "delete", "status", "renew"):
510 parser.add_option("-C", "--credential", dest='show_credential', action='store_true', default=False,
511 help="show credential(s) used in human-readable form")
512 if canonical in ("renew"):
513 parser.add_option("-l", "--as-long-as-possible", dest='alap', action='store_true', default=False,
514 help="renew as long as possible")
515 # registy filter option
516 if canonical in ("list", "show", "remove"):
517 parser.add_option("-t", "--type", dest="type", metavar="<type>",
519 help="type filter - 2 first chars is enough ([all]|user|slice|authority|node|aggregate)")
520 if canonical in ("show"):
521 parser.add_option("-k", "--key", dest="keys", action="append", default=[],
522 help="specify specific keys to be displayed from record")
523 parser.add_option("-n", "--no-details", dest="no_details", action="store_true", default=False,
524 help="call Resolve without the 'details' option")
525 if canonical in ("resources", "describe"):
527 parser.add_option("-r", "--rspec-version", dest="rspec_version", default=DEFAULT_RSPEC_VERSION,
528 help="schema type and version of resulting RSpec (default:{})".format(DEFAULT_RSPEC_VERSION))
529 # disable/enable cached rspecs
530 parser.add_option("-c", "--current", dest="current", default=False,
532 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
534 parser.add_option("-f", "--format", dest="format", type="choice",
535 help="display format ([xml]|dns|ip)", default="xml",
536 choices=("xml", "dns", "ip"))
537 # panos: a new option to define the type of information about
538 # resources a user is interested in
539 parser.add_option("-i", "--info", dest="info",
540 help="optional component information", default=None)
541 # a new option to retrieve or not reservation-oriented RSpecs
543 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
544 help="Retrieve or not reservation-oriented RSpecs ([resources]|leases|all)",
545 choices=("all", "resources", "leases"), default="resources")
547 if canonical in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
548 parser.add_option("-o", "--output", dest="file",
549 help="output XML to file", metavar="FILE", default=None)
551 if canonical in ("show", "list"):
552 parser.add_option("-f", "--format", dest="format", type="choice",
553 help="display format ([text]|xml)", default="text",
554 choices=("text", "xml"))
556 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
557 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
558 choices=("xml", "xmllist", "hrnlist"))
559 if canonical == 'list':
560 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
561 help="list all child records", default=False)
562 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
563 help="gives details, like user keys", default=False)
564 if canonical in ("delegate"):
565 parser.add_option("-u", "--user",
566 action="store_true", dest="delegate_user", default=False,
567 help="delegate your own credentials; default if no other option is provided")
568 parser.add_option("-s", "--slice", dest="delegate_slices", action='append', default=[],
569 metavar="slice_hrn", help="delegate cred. for slice HRN")
570 parser.add_option("-a", "--auths", dest='delegate_auths', action='append', default=[],
571 metavar='auth_hrn', help="delegate cred for auth HRN")
572 # this primarily is a shorthand for -A my_hrn^
573 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
574 help="delegate your PI credentials, so s.t. like -A your_hrn^")
575 parser.add_option("-A", "--to-authority", dest='delegate_to_authority', action='store_true', default=False,
576 help="""by default the mandatory argument is expected to be a user,
577 use this if you mean an authority instead""")
579 if canonical in ("myslice"):
580 parser.add_option("-p", "--password", dest='password', action='store', default=None,
581 help="specify mainfold password on the command line")
582 parser.add_option("-s", "--slice", dest="delegate_slices", action='append', default=[],
583 metavar="slice_hrn", help="delegate cred. for slice HRN")
584 parser.add_option("-a", "--auths", dest='delegate_auths', action='append', default=[],
585 metavar='auth_hrn', help="delegate PI cred for auth HRN")
586 parser.add_option('-d', '--delegate', dest='delegate',
587 help="Override 'delegate' from the config file")
588 parser.add_option('-b', '--backend', dest='backend',
589 help="Override 'backend' from the config file")
594 # Main: parse arguments and dispatch to command
596 def dispatch(self, command, command_options, command_args):
597 (doc, args_string, example, canonical) = commands_dict[command]
598 method = getattr(self, canonical, None)
600 print("sfi: unknown command {}".format(command))
601 raise SystemExit("Unknown command {}".format(command))
602 for arg in command_args:
603 if 'help' in arg or arg == '-h':
606 return method(command_options, command_args)
610 self.sfi_parser = self.create_parser_global()
611 (options, args) = self.sfi_parser.parse_args()
613 self.print_commands_help(options)
615 self.options = options
617 self.logger.setLevelFromOptVerbose(self.options.verbose)
620 self.logger.critical("No command given. Use -h for help.")
621 self.print_commands_help(options)
624 # complete / find unique match with command set
625 command_candidates = Candidates(commands_list)
627 command = command_candidates.only_match(input)
629 self.print_commands_help(options)
631 # second pass options parsing
632 self.command = command
633 self.command_parser = self.create_parser_command(command)
634 (command_options, command_args) = self.command_parser.parse_args(
636 if command_options.help:
639 self.command_options = command_options
641 # allow incoming types on 2 characters only
642 if hasattr(command_options, 'type'):
643 command_options.type = normalize_type(command_options.type)
644 if not command_options.type:
649 self.logger.debug("Command={}".format(self.command))
652 retcod = self.dispatch(command, command_options, command_args)
656 self.logger.log_exc("sfi command {} failed".format(command))
661 def read_config(self):
662 config_file = os.path.join(self.options.sfi_dir, "sfi_config")
663 shell_config_file = os.path.join(self.options.sfi_dir, "sfi_config.sh")
665 if Config.is_ini(config_file):
666 config = Config(config_file)
668 # try upgrading from shell config format
669 fp, fn = mkstemp(suffix='sfi_config', text=True)
671 # we need to preload the sections we want parsed
672 # from the shell config
673 config.add_section('sfi')
674 # sface users should be able to use this same file to configure
676 config.add_section('sface')
677 # manifold users should be able to specify the details
678 # of their backend server here for 'sfi myslice'
679 config.add_section('myslice')
680 config.load(config_file)
682 shutil.move(config_file, shell_config_file)
684 config.save(config_file)
687 self.logger.critical(
688 "Failed to read configuration file {}".format(config_file))
690 "Make sure to remove the export clauses and to add quotes")
691 if self.options.verbose == 0:
692 self.logger.info("Re-run with -v for more details")
695 "Could not read config file {}".format(config_file))
698 self.config_instance = config
701 if (self.options.sm is not None):
702 self.sm_url = self.options.sm
703 elif hasattr(config, "SFI_SM"):
704 self.sm_url = config.SFI_SM
707 "You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in {}".format(config_file))
711 if (self.options.registry is not None):
712 self.reg_url = self.options.registry
713 elif hasattr(config, "SFI_REGISTRY"):
714 self.reg_url = config.SFI_REGISTRY
717 "You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in {}".format(config_file))
721 if (self.options.user is not None):
722 self.user = self.options.user
723 elif hasattr(config, "SFI_USER"):
724 self.user = config.SFI_USER
727 "You need to set e.g. SFI_USER='plc.princeton.username' in {}".format(config_file))
731 if (self.options.auth is not None):
732 self.authority = self.options.auth
733 elif hasattr(config, "SFI_AUTH"):
734 self.authority = config.SFI_AUTH
737 "You need to set e.g. SFI_AUTH='plc.princeton' in {}".format(config_file))
740 self.config_file = config_file
745 # Get various credential and spec files
747 # Establishes limiting conventions
748 # - conflates MAs and SAs
749 # - assumes last token in slice name is unique
751 # Bootstraps credentials
752 # - bootstrap user credential from self-signed certificate
753 # - bootstrap authority credential from user credential
754 # - bootstrap slice credential from user credential
757 # init self-signed cert, user credentials and gid
759 if self.options.verbose:
761 "Initializing SfaClientBootstrap with {}".format(self.reg_url))
762 client_bootstrap = SfaClientBootstrap(self.user, self.reg_url, self.options.sfi_dir,
764 # if -k is provided, use this to initialize private key
765 if self.options.user_private_key:
766 client_bootstrap.init_private_key_if_missing(
767 self.options.user_private_key)
769 # trigger legacy compat code if needed
770 # the name has changed from just <leaf>.pkey to <hrn>.pkey
771 if not os.path.isfile(client_bootstrap.private_key_filename()):
772 self.logger.info("private key not found, trying legacy name")
774 legacy_private_key = os.path.join(self.options.sfi_dir, "{}.pkey"
775 .format(Xrn.unescape(get_leaf(self.user))))
776 self.logger.debug("legacy_private_key={}"
777 .format(legacy_private_key))
778 client_bootstrap.init_private_key_if_missing(
780 self.logger.info("Copied private key from legacy location {}"
781 .format(legacy_private_key))
783 self.logger.log_exc("Can't find private key ")
787 client_bootstrap.bootstrap_my_gid()
788 # extract what's needed
789 self.private_key = client_bootstrap.private_key()
790 self.my_credential_string = client_bootstrap.my_credential_string()
791 self.my_credential = {'geni_type': 'geni_sfa',
793 'geni_value': self.my_credential_string}
794 self.my_gid = client_bootstrap.my_gid()
795 self.client_bootstrap = client_bootstrap
797 def my_authority_credential_string(self):
798 if not self.authority:
799 self.logger.critical(
800 "no authority specified. Use -a or set SF_AUTH")
802 return self.client_bootstrap.authority_credential_string(self.authority)
804 def authority_credential_string(self, auth_hrn):
805 return self.client_bootstrap.authority_credential_string(auth_hrn)
807 def slice_credential_string(self, name):
808 return self.client_bootstrap.slice_credential_string(name)
810 def slice_credential(self, name):
811 return {'geni_type': 'geni_sfa',
813 'geni_value': self.slice_credential_string(name)}
815 # xxx should be supported by sfaclientbootstrap as well
816 def delegate_cred(self, object_cred, hrn, type='authority'):
817 # the gid and hrn of the object we are delegating
818 if isinstance(object_cred, str):
819 object_cred = Credential(string=object_cred)
820 object_gid = object_cred.get_gid_object()
821 object_hrn = object_gid.get_hrn()
823 if not object_cred.get_privileges().get_all_delegate():
824 self.logger.error("Object credential {} does not have delegate bit set"
828 # the delegating user's gid
829 caller_gidfile = self.my_gid()
831 # the gid of the user who will be delegated to
832 delegee_gid = self.client_bootstrap.gid(hrn, type)
833 delegee_hrn = delegee_gid.get_hrn()
834 dcred = object_cred.delegate(
835 delegee_gid, self.private_key, caller_gidfile)
836 return dcred.save_to_string(save_parents=True)
839 # Management of the servers
844 if not hasattr(self, 'registry_proxy'):
845 self.logger.info("Contacting Registry at: {}".format(self.reg_url))
846 self.registry_proxy \
847 = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
848 timeout=self.options.timeout, verbose=self.options.debug)
849 return self.registry_proxy
853 if not hasattr(self, 'sliceapi_proxy'):
854 # if the command exposes the --component option, figure it's
855 # hostname and connect at CM_PORT
856 if hasattr(self.command_options, 'component') and self.command_options.component:
857 # resolve the hrn at the registry
858 node_hrn = self.command_options.component
859 records = self.registry().Resolve(node_hrn, self.my_credential_string)
860 records = filter_records('node', records)
863 "No such component:{}".format(opts.component))
865 cm_url = "http://{}:{}/".format(record['hostname'], CM_PORT)
866 self.sliceapi_proxy = SfaServerProxy(
867 cm_url, self.private_key, self.my_gid)
869 # otherwise use what was provided as --sliceapi, or SFI_SM in
871 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
872 self.sm_url = 'http://' + self.sm_url
874 "Contacting Slice Manager at: {}".format(self.sm_url))
875 self.sliceapi_proxy \
876 = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
877 timeout=self.options.timeout, verbose=self.options.debug)
878 return self.sliceapi_proxy
880 def get_cached_server_version(self, server):
881 # check local cache first
884 cache_file = os.path.join(self.options.sfi_dir, 'sfi_cache.dat')
885 cache_key = server.url + "-version"
887 cache = Cache(cache_file)
890 self.logger.info("Local cache not found at: {}".format(cache_file))
893 version = cache.get(cache_key)
896 result = server.GetVersion()
897 version = ReturnValue.get_value(result)
898 # cache version for 20 minutes
899 cache.add(cache_key, version, ttl=60 * 20)
900 self.logger.info("Updating cache file {}".format(cache_file))
901 cache.save_to_file(cache_file)
905 # resurrect this temporarily so we can support V1 aggregates for a while
906 def server_supports_options_arg(self, server):
908 Returns true if server support the optional call_id arg, false otherwise.
910 server_version = self.get_cached_server_version(server)
912 # xxx need to rewrite this
913 if int(server_version.get('geni_api')) >= 2:
917 def server_supports_call_id_arg(self, server):
918 server_version = self.get_cached_server_version(server)
920 if 'sfa' in server_version and 'code_tag' in server_version:
921 code_tag = server_version['code_tag']
922 code_tag_parts = code_tag.split("-")
923 version_parts = code_tag_parts[0].split(".")
924 major, minor = version_parts[0], version_parts[1]
925 rev = code_tag_parts[1]
926 if int(major) == 1 and minor == 0 and build >= 22:
930 # ois = options if supported
931 # to be used in something like serverproxy.Method(arg1, arg2,
932 # *self.ois(api_options))
933 def ois(self, server, option_dict):
934 if self.server_supports_options_arg(server):
936 elif self.server_supports_call_id_arg(server):
937 return [unique_call_id()]
941 # cis = call_id if supported - like ois
942 def cis(self, server):
943 if self.server_supports_call_id_arg(server):
944 return [unique_call_id]
949 def get_rspec_file(self, rspec):
950 if (os.path.isabs(rspec)):
953 file = os.path.join(self.options.sfi_dir, rspec)
954 if (os.path.isfile(file)):
957 self.logger.critical("No such rspec file {}".format(rspec))
960 def get_record_file(self, record):
961 if (os.path.isabs(record)):
964 file = os.path.join(self.options.sfi_dir, record)
965 if (os.path.isfile(file)):
968 self.logger.critical(
969 "No such registry record file {}".format(record))
972 # helper function to analyze raw output
973 # for main : return 0 if everything is fine, something else otherwise
975 def success(self, raw):
976 return_value = ReturnValue(raw)
977 output = ReturnValue.get_output(return_value)
978 # means everything is fine
981 # something went wrong
982 print('ERROR:', output)
985 #==========================================================================
986 # Following functions implement the commands
988 # Registry-related commands
989 #==========================================================================
991 @declare_command("", "")
992 def config(self, options, args):
994 Display contents of current config
1000 print("# From configuration file {}".format(self.config_file))
1001 flags = [('sfi', [('registry', 'reg_url'),
1002 ('auth', 'authority'),
1009 ('myslice', ['backend', 'delegate', 'platform', 'username']))
1011 for (section, tuples) in flags:
1012 print("[{}]".format(section))
1014 for external_name, internal_name in tuples:
1015 print("{:<20} = {}".format(
1016 external_name, getattr(self, internal_name)))
1018 for external_name, internal_name in tuples:
1019 varname = "{}_{}".format(
1020 section.upper(), external_name.upper())
1021 value = getattr(self.config_instance, varname)
1022 print("{:<20} = {}".format(external_name, value))
1023 # xxx should analyze result
1026 @declare_command("", "")
1027 def version(self, options, args):
1029 display an SFA server version (GetVersion)
1030 or version information about sfi itself
1036 if options.version_local:
1037 version = version_core()
1039 if options.registry_interface:
1040 server = self.registry()
1042 server = self.sliceapi()
1043 result = server.GetVersion()
1044 version = ReturnValue.get_value(result)
1045 if self.options.raw:
1046 save_raw_to_file(result, self.options.raw,
1047 self.options.rawformat, self.options.rawbanner)
1049 pprinter = PrettyPrinter(indent=4)
1050 pprinter.pprint(version)
1051 # xxx should analyze result
1054 @declare_command("authority", "")
1055 def list(self, options, args):
1057 list entries in named authority registry (List)
1065 if options.recursive:
1066 opts['recursive'] = options.recursive
1068 if options.show_credential:
1069 show_credentials(self.my_credential_string)
1071 list = self.registry().List(hrn, self.my_credential_string, options)
1073 raise Exception("Not enough parameters for the 'list' command")
1075 # filter on person, slice, site, node, etc.
1076 # This really should be in the self.filter_records funct def comment...
1077 list = filter_records(options.type, list)
1078 terminal_render(list, options)
1080 save_records_to_file(options.file, list, options.fileformat)
1081 # xxx should analyze result
1084 @declare_command("name", "")
1085 def show(self, options, args):
1087 show details about named registry record (Resolve)
1094 # explicitly require Resolve to run in details mode
1095 resolve_options = {}
1096 if not options.no_details:
1097 resolve_options['details'] = True
1098 record_dicts = self.registry().Resolve(
1099 hrn, self.my_credential_string, resolve_options)
1100 record_dicts = filter_records(options.type, record_dicts)
1101 if not record_dicts:
1102 self.logger.error("No record of type {}".format(options.type))
1104 # user has required to focus on some keys
1106 def project(record):
1108 for key in options.keys:
1110 projected[key] = record[key]
1114 record_dicts = [project(record) for record in record_dicts]
1115 records = [Record(dict=record_dict) for record_dict in record_dicts]
1116 for record in records:
1117 if (options.format == "text"):
1118 record.dump(sort=True)
1120 print(record.save_as_xml())
1122 save_records_to_file(
1123 options.file, record_dicts, options.fileformat)
1124 # xxx should analyze result
1127 # this historically was named 'add', it is now 'register' with an alias
1129 @declare_command("[xml-filename]", "", ['add'])
1130 def register(self, options, args):
1132 create new record in registry (Register)
1133 from command line options (recommended)
1134 old-school method involving an xml file still supported
1140 auth_cred = self.my_authority_credential_string()
1141 if options.show_credential:
1142 show_credentials(auth_cred)
1146 record_filepath = args[0]
1147 rec_file = self.get_record_file(record_filepath)
1148 record_dict.update(load_record_from_file(
1149 rec_file).record_to_dict())
1151 print("Cannot load record file {}".format(record_filepath))
1154 record_dict.update(load_record_from_opts(options).record_to_dict())
1155 # we should have a type by now
1156 if 'type' not in record_dict:
1159 # this is still planetlab dependent.. as plc will whine without that
1160 # also, it's only for adding
1161 if record_dict['type'] == 'user':
1162 if not 'first_name' in record_dict:
1163 record_dict['first_name'] = record_dict['hrn']
1164 if 'last_name' not in record_dict:
1165 record_dict['last_name'] = record_dict['hrn']
1166 register = self.registry().Register(record_dict, auth_cred)
1167 # xxx looks like the result here is not ReturnValue-compatible
1168 # return self.success (register)
1169 # xxx should analyze result
1172 @declare_command("[xml-filename]", "")
1173 def update(self, options, args):
1175 update record into registry (Update)
1176 from command line options (recommended)
1177 old-school method involving an xml file still supported
1185 record_filepath = args[0]
1186 rec_file = self.get_record_file(record_filepath)
1187 record_dict.update(load_record_from_file(
1188 rec_file).record_to_dict())
1190 record_dict.update(load_record_from_opts(options).record_to_dict())
1191 # at the very least we need 'type' here
1192 if 'type' not in record_dict or record_dict['type'] is None:
1196 # don't translate into an object, as this would possibly distort
1197 # user-provided data; e.g. add an 'email' field to Users
1198 if record_dict['type'] in ['user']:
1199 if record_dict['hrn'] == self.user:
1200 cred = self.my_credential_string
1202 cred = self.my_authority_credential_string()
1203 elif record_dict['type'] in ['slice']:
1205 cred = self.slice_credential_string(record_dict['hrn'])
1206 except ServerException as e:
1207 # XXX smbaker -- once we have better error return codes, update this
1208 # to do something better than a string compare
1209 if "Permission error" in e.args[0]:
1210 cred = self.my_authority_credential_string()
1213 elif record_dict['type'] in ['authority']:
1214 cred = self.my_authority_credential_string()
1215 elif record_dict['type'] in ['node']:
1216 cred = self.my_authority_credential_string()
1219 "unknown record type {}".format(record_dict['type']))
1220 if options.show_credential:
1221 show_credentials(cred)
1222 update = self.registry().Update(record_dict, cred)
1223 # xxx looks like the result here is not ReturnValue-compatible
1224 # return self.success(update)
1225 # xxx should analyze result
1228 @declare_command("hrn", "")
1229 def remove(self, options, args):
1231 remove registry record by name (Remove)
1233 auth_cred = self.my_authority_credential_string()
1242 if options.show_credential:
1243 show_credentials(auth_cred)
1244 remove = self.registry().Remove(hrn, auth_cred, type)
1245 # xxx looks like the result here is not ReturnValue-compatible
1246 # return self.success (remove)
1247 # xxx should analyze result
1250 # ==================================================================
1251 # Slice-related commands
1252 # ==================================================================
1254 # show rspec for named slice
1255 @declare_command("", "", ['discover'])
1256 def resources(self, options, args):
1258 discover available resources (ListResources)
1264 server = self.sliceapi()
1266 creds = [self.my_credential_string]
1267 if options.delegate:
1268 creds.append(self.delegate_cred(
1269 cred, get_authority(self.authority)))
1270 if options.show_credential:
1271 show_credentials(creds)
1273 # no need to check if server accepts the options argument since the options has
1274 # been a required argument since v1 API
1276 # always send call_id to v2 servers
1277 api_options['call_id'] = unique_call_id()
1278 # ask for cached value if available
1279 api_options['cached'] = True
1281 api_options['info'] = options.info
1282 if options.list_leases:
1283 api_options['list_leases'] = options.list_leases
1285 if options.current == True:
1286 api_options['cached'] = False
1288 api_options['cached'] = True
1289 version_manager = VersionManager()
1290 api_options['geni_rspec_version'] = version_manager.get_version(
1291 options.rspec_version).to_dict()
1293 list_resources = server.ListResources(creds, api_options)
1294 value = ReturnValue.get_value(list_resources)
1295 if self.options.raw:
1296 save_raw_to_file(list_resources, self.options.raw,
1297 self.options.rawformat, self.options.rawbanner)
1298 if options.file is not None:
1299 save_rspec_to_file(value, options.file)
1300 if (self.options.raw is None) and (options.file is None):
1301 display_rspec(value, options.format)
1302 return self.success(list_resources)
1304 @declare_command("slice_hrn", "")
1305 def describe(self, options, args):
1307 shows currently allocated/provisioned resources
1308 of the named slice or set of slivers (Describe)
1314 server = self.sliceapi()
1316 creds = [self.slice_credential(args[0])]
1317 if options.delegate:
1318 creds.append(self.delegate_cred(
1319 cred, get_authority(self.authority)))
1320 if options.show_credential:
1321 show_credentials(creds)
1323 api_options = {'call_id': unique_call_id(),
1325 'info': options.info,
1326 'list_leases': options.list_leases,
1327 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1330 api_options['info'] = options.info
1332 if options.rspec_version:
1333 version_manager = VersionManager()
1334 server_version = self.get_cached_server_version(server)
1335 if 'sfa' in server_version:
1336 # just request the version the client wants
1337 api_options['geni_rspec_version'] = version_manager.get_version(
1338 options.rspec_version).to_dict()
1340 api_options['geni_rspec_version'] = {
1341 'type': 'geni', 'version': '3'}
1342 urn = Xrn(args[0], type='slice').get_urn()
1343 remove_none_fields(api_options)
1344 describe = server.Describe([urn], creds, api_options)
1345 value = ReturnValue.get_value(describe)
1346 if self.options.raw:
1347 save_raw_to_file(describe, self.options.raw,
1348 self.options.rawformat, self.options.rawbanner)
1349 if options.file is not None:
1350 save_rspec_to_file(value['geni_rspec'], options.file)
1351 if (self.options.raw is None) and (options.file is None):
1352 display_rspec(value['geni_rspec'], options.format)
1353 return self.success(describe)
1355 @declare_command("slice_hrn [<sliver_urn>...]", "")
1356 def delete(self, options, args):
1358 de-allocate and de-provision all or named slivers of the named slice (Delete)
1364 server = self.sliceapi()
1367 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1370 # we have sliver urns
1371 sliver_urns = args[1:]
1373 # we provision all the slivers of the slice
1374 sliver_urns = [slice_urn]
1377 slice_cred = self.slice_credential(slice_hrn)
1378 creds = [slice_cred]
1380 # options and call_id when supported
1382 api_options['call_id'] = unique_call_id()
1383 if options.show_credential:
1384 show_credentials(creds)
1385 delete = server.Delete(sliver_urns, creds, *
1386 self.ois(server, api_options))
1387 value = ReturnValue.get_value(delete)
1388 if self.options.raw:
1389 save_raw_to_file(delete, self.options.raw,
1390 self.options.rawformat, self.options.rawbanner)
1393 return self.success(delete)
1395 @declare_command("slice_hrn rspec", "")
1396 def allocate(self, options, args):
1398 allocate resources to the named slice (Allocate)
1404 server = self.sliceapi()
1405 server_version = self.get_cached_server_version(server)
1407 rspec_file = self.get_rspec_file(args[1])
1409 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1412 creds = [self.slice_credential(slice_hrn)]
1414 delegated_cred = None
1415 if server_version.get('interface') == 'slicemgr':
1416 # delegate our cred to the slice manager
1417 # do not delegate cred to slicemgr...not working at the moment
1419 # if server_version.get('hrn'):
1420 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1421 # elif server_version.get('urn'):
1422 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1424 if options.show_credential:
1425 show_credentials(creds)
1429 api_options['call_id'] = unique_call_id()
1433 slice_records = self.registry().Resolve(
1434 slice_urn, [self.my_credential_string])
1435 remove_none_fields(slice_records[0])
1436 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers'] != []:
1437 slice_record = slice_records[0]
1438 user_hrns = slice_record['reg-researchers']
1439 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1440 user_records = self.registry().Resolve(
1441 user_urns, [self.my_credential_string])
1442 sfa_users = sfa_users_arg(user_records, slice_record)
1443 geni_users = pg_users_arg(user_records)
1445 api_options['sfa_users'] = sfa_users
1446 api_options['geni_users'] = geni_users
1448 with open(rspec_file) as rspec:
1449 rspec_xml = rspec.read()
1450 allocate = server.Allocate(
1451 slice_urn, creds, rspec_xml, api_options)
1452 value = ReturnValue.get_value(allocate)
1453 if self.options.raw:
1454 save_raw_to_file(allocate, self.options.raw,
1455 self.options.rawformat, self.options.rawbanner)
1456 if options.file is not None:
1457 save_rspec_to_file(value['geni_rspec'], options.file)
1458 if (self.options.raw is None) and (options.file is None):
1460 return self.success(allocate)
1462 @declare_command("slice_hrn [<sliver_urn>...]", "")
1463 def provision(self, options, args):
1465 provision all or named already allocated slivers of the named slice (Provision)
1471 server = self.sliceapi()
1472 server_version = self.get_cached_server_version(server)
1474 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1476 # we have sliver urns
1477 sliver_urns = args[1:]
1479 # we provision all the slivers of the slice
1480 sliver_urns = [slice_urn]
1483 creds = [self.slice_credential(slice_hrn)]
1484 delegated_cred = None
1485 if server_version.get('interface') == 'slicemgr':
1486 # delegate our cred to the slice manager
1487 # do not delegate cred to slicemgr...not working at the moment
1489 # if server_version.get('hrn'):
1490 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1491 # elif server_version.get('urn'):
1492 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1494 if options.show_credential:
1495 show_credentials(creds)
1498 api_options['call_id'] = unique_call_id()
1500 # set the requtested rspec version
1501 version_manager = VersionManager()
1502 rspec_version = version_manager._get_version('geni', '3').to_dict()
1503 api_options['geni_rspec_version'] = rspec_version
1506 # need to pass along user keys to the aggregate.
1508 # { urn: urn:publicid:IDN+emulab.net+user+alice
1509 # keys: [<ssh key A>, <ssh key B>]
1512 slice_records = self.registry().Resolve(
1513 slice_urn, [self.my_credential_string])
1514 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers'] != []:
1515 slice_record = slice_records[0]
1516 user_hrns = slice_record['reg-researchers']
1517 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1518 user_records = self.registry().Resolve(
1519 user_urns, [self.my_credential_string])
1520 users = pg_users_arg(user_records)
1522 api_options['geni_users'] = users
1523 provision = server.Provision(sliver_urns, creds, api_options)
1524 value = ReturnValue.get_value(provision)
1525 if self.options.raw:
1526 save_raw_to_file(provision, self.options.raw,
1527 self.options.rawformat, self.options.rawbanner)
1528 if options.file is not None:
1529 save_rspec_to_file(value['geni_rspec'], options.file)
1530 if (self.options.raw is None) and (options.file is None):
1532 return self.success(provision)
1534 @declare_command("slice_hrn", "")
1535 def status(self, options, args):
1537 retrieve the status of the slivers belonging to the named slice (Status)
1543 server = self.sliceapi()
1546 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1549 slice_cred = self.slice_credential(slice_hrn)
1550 creds = [slice_cred]
1552 # options and call_id when supported
1554 api_options['call_id'] = unique_call_id()
1555 if options.show_credential:
1556 show_credentials(creds)
1557 status = server.Status([slice_urn], creds, *
1558 self.ois(server, api_options))
1559 value = ReturnValue.get_value(status)
1560 if self.options.raw:
1561 save_raw_to_file(status, self.options.raw,
1562 self.options.rawformat, self.options.rawbanner)
1565 return self.success(status)
1567 @declare_command("slice_hrn [<sliver_urn>...] action", "")
1568 def action(self, options, args):
1570 Perform the named operational action on all or named slivers of the named slice
1576 server = self.sliceapi()
1580 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1582 # we have sliver urns
1583 sliver_urns = args[1:-1]
1585 # we provision all the slivers of the slice
1586 sliver_urns = [slice_urn]
1589 slice_cred = self.slice_credential(args[0])
1590 creds = [slice_cred]
1591 if options.delegate:
1592 delegated_cred = self.delegate_cred(
1593 slice_cred, get_authority(self.authority))
1594 creds.append(delegated_cred)
1596 perform_action = server.PerformOperationalAction(
1597 sliver_urns, creds, action, api_options)
1598 value = ReturnValue.get_value(perform_action)
1599 if self.options.raw:
1600 save_raw_to_file(perform_action, self.options.raw,
1601 self.options.rawformat, self.options.rawbanner)
1604 return self.success(perform_action)
1606 @declare_command("slice_hrn [<sliver_urn>...] time",
1607 "\n".join(["sfi renew onelab.ple.heartbeat 2015-04-31",
1608 "sfi renew onelab.ple.heartbeat 2015-04-31T14:00:00Z",
1609 "sfi renew onelab.ple.heartbeat +5d",
1610 "sfi renew onelab.ple.heartbeat +3w",
1611 "sfi renew onelab.ple.heartbeat +2m", ]))
1612 def renew(self, options, args):
1620 server = self.sliceapi()
1622 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1625 # we have sliver urns
1626 sliver_urns = args[1:-1]
1628 # we provision all the slivers of the slice
1629 sliver_urns = [slice_urn]
1630 input_time = args[-1]
1632 # time: don't try to be smart on the time format, server-side will
1634 slice_cred = self.slice_credential(args[0])
1635 creds = [slice_cred]
1636 # options and call_id when supported
1638 api_options['call_id'] = unique_call_id()
1640 api_options['geni_extend_alap'] = True
1641 if options.show_credential:
1642 show_credentials(creds)
1643 renew = server.Renew(sliver_urns, creds, input_time,
1644 *self.ois(server, api_options))
1645 value = ReturnValue.get_value(renew)
1646 if self.options.raw:
1647 save_raw_to_file(renew, self.options.raw,
1648 self.options.rawformat, self.options.rawbanner)
1651 return self.success(renew)
1653 @declare_command("slice_hrn", "")
1654 def shutdown(self, options, args):
1656 shutdown named slice (Shutdown)
1662 server = self.sliceapi()
1665 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1667 slice_cred = self.slice_credential(slice_hrn)
1668 creds = [slice_cred]
1669 shutdown = server.Shutdown(slice_urn, creds)
1670 value = ReturnValue.get_value(shutdown)
1671 if self.options.raw:
1672 save_raw_to_file(shutdown, self.options.raw,
1673 self.options.rawformat, self.options.rawbanner)
1676 return self.success(shutdown)
1678 @declare_command("[name]", "")
1679 def gid(self, options, args):
1681 Create a GID (CreateGid)
1687 target_hrn = args[0]
1688 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1689 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1691 filename = options.file
1693 filename = os.sep.join(
1694 [self.options.sfi_dir, '{}.gid'.format(target_hrn)])
1695 self.logger.info("writing {} gid to {}".format(target_hrn, filename))
1696 GID(string=gid).save_to_file(filename)
1697 # xxx should analyze result
1700 ####################
1701 @declare_command("to_hrn", """$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1703 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1704 the set of credentials in the scope for this call would be
1705 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1707 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1709 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1710 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1711 because of the two -s options
1714 def delegate(self, options, args):
1716 (locally) create delegate credential for use by given hrn
1717 make sure to check for 'sfi myslice' instead if you plan
1725 # support for several delegations in the same call
1726 # so first we gather the things to do
1728 for slice_hrn in options.delegate_slices:
1729 message = "{}.slice".format(slice_hrn)
1730 original = self.slice_credential_string(slice_hrn)
1731 tuples.append((message, original,))
1732 if options.delegate_pi:
1733 my_authority = self.authority
1734 message = "{}.pi".format(my_authority)
1735 original = self.my_authority_credential_string()
1736 tuples.append((message, original,))
1737 for auth_hrn in options.delegate_auths:
1738 message = "{}.auth".format(auth_hrn)
1739 original = self.authority_credential_string(auth_hrn)
1740 tuples.append((message, original, ))
1741 # if nothing was specified at all at this point, let's assume -u
1743 options.delegate_user = True
1745 if options.delegate_user:
1746 message = "{}.user".format(self.user)
1747 original = self.my_credential_string
1748 tuples.append((message, original, ))
1750 # default type for beneficial is user unless -A
1751 to_type = 'authority' if options.delegate_to_authority else 'user'
1753 # let's now handle all this
1754 # it's all in the filenaming scheme
1755 for (message, original) in tuples:
1756 delegated_string = self.client_bootstrap.delegate_credential_string(
1757 original, to_hrn, to_type)
1758 delegated_credential = Credential(string=delegated_string)
1759 filename = os.path.join(self.options.sfi_dir,
1760 "{}_for_{}.{}.cred".format(message, to_hrn, to_type))
1761 delegated_credential.save_to_file(filename, save_parents=True)
1762 self.logger.info("delegated credential for {} to {} and wrote to {}"
1763 .format(message, to_hrn, filename))
1765 ####################
1766 @declare_command("", """$ less +/myslice sfi_config
1768 backend = http://manifold.pl.sophia.inria.fr:7080
1769 # the HRN that myslice uses, so that we are delegating to
1770 delegate = ple.upmc.slicebrowser
1771 # platform - this is a myslice concept
1773 # username - as of this writing (May 2013) a simple login name
1777 will first collect the slices that you are part of, then make sure
1778 all your credentials are up-to-date (read: refresh expired ones)
1779 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1780 and upload them all on myslice backend, using 'platform' and 'user'.
1781 A password will be prompted for the upload part.
1783 $ sfi -v myslice -- or sfi -vv myslice
1784 same but with more and more verbosity
1786 $ sfi m -b http://mymanifold.foo.com:7080/
1787 is synonym to sfi myslice as no other command starts with an 'm'
1788 and uses a custom backend for this one call
1791 def myslice(self, options, args):
1792 """ This helper is for refreshing your credentials at myslice; it will
1793 * compute all the slices that you currently have credentials on
1794 * refresh all your credentials (you as a user and pi, your slices)
1795 * upload them to the manifold backend server
1796 for last phase, sfi_config is read to look for the [myslice] section,
1797 and namely the 'backend', 'delegate' and 'user' settings
1804 # enable info by default
1805 self.logger.setLevelFromOptVerbose(self.options.verbose + 1)
1806 # the rough sketch goes like this
1807 # (0) produce a p12 file
1808 self.client_bootstrap.my_pkcs12()
1810 # (a) rain check for sufficient config in sfi_config
1812 myslice_keys = ['backend', 'delegate', 'platform', 'username']
1813 for key in myslice_keys:
1815 # oct 2013 - I'm finding myself juggling with config files
1816 # so a couple of command-line options can now override config
1817 if hasattr(options, key) and getattr(options, key) is not None:
1818 value = getattr(options, key)
1820 full_key = "MYSLICE_" + key.upper()
1821 value = getattr(self.config_instance, full_key, None)
1823 myslice_dict[key] = value
1825 print("Unsufficient config, missing key {} in [myslice] section of sfi_config"
1827 if len(myslice_dict) != len(myslice_keys):
1830 # (b) figure whether we are PI for the authority where we belong
1831 self.logger.info("Resolving our own id {}".format(self.user))
1832 my_records = self.registry().Resolve(self.user, self.my_credential_string)
1833 if len(my_records) != 1:
1834 print("Cannot Resolve {} -- exiting".format(self.user))
1836 my_record = my_records[0]
1837 my_auths_all = my_record['reg-pi-authorities']
1839 "Found {} authorities that we are PI for".format(len(my_auths_all)))
1840 self.logger.debug("They are {}".format(my_auths_all))
1842 my_auths = my_auths_all
1843 if options.delegate_auths:
1844 my_auths = list(set(my_auths_all).intersection(
1845 set(options.delegate_auths)))
1847 "Restricted to user-provided auths {}".format(my_auths))
1849 # (c) get the set of slices that we are in
1850 my_slices_all = my_record['reg-slices']
1852 "Found {} slices that we are member of".format(len(my_slices_all)))
1853 self.logger.debug("They are: {}".format(my_slices_all))
1855 my_slices = my_slices_all
1856 # if user provided slices, deal only with these - if they are found
1857 if options.delegate_slices:
1858 my_slices = list(set(my_slices_all).intersection(
1859 set(options.delegate_slices)))
1861 "Restricted to user-provided slices: {}".format(my_slices))
1863 # (d) make sure we have *valid* credentials for all these
1864 hrn_credentials = []
1865 hrn_credentials.append((self.user, 'user', self.my_credential_string,))
1866 for auth_hrn in my_auths:
1867 hrn_credentials.append(
1868 (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),))
1869 for slice_hrn in my_slices:
1871 hrn_credentials.append(
1872 (slice_hrn, 'slice', self.slice_credential_string(slice_hrn),))
1874 print("WARNING: could not get slice credential for slice {}"
1877 # (e) check for the delegated version of these
1878 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1879 # switch to myslice using an authority instead of a user
1880 delegatee_type = 'user'
1881 delegatee_hrn = myslice_dict['delegate']
1882 hrn_delegated_credentials = []
1883 for (hrn, htype, credential) in hrn_credentials:
1884 delegated_credential = self.client_bootstrap.delegate_credential_string(
1885 credential, delegatee_hrn, delegatee_type)
1886 # save these so user can monitor what she's uploaded
1887 filename = os.path.join(self.options.sfi_dir,
1888 "{}.{}_for_{}.{}.cred"
1889 .format(hrn, htype, delegatee_hrn, delegatee_type))
1890 with open(filename, 'w') as f:
1891 f.write(delegated_credential)
1892 self.logger.debug("(Over)wrote {}".format(filename))
1893 hrn_delegated_credentials.append(
1894 (hrn, htype, delegated_credential, filename, ))
1896 # (f) and finally upload them to manifold server
1897 # xxx todo add an option so the password can be set on the command line
1898 # (but *NOT* in the config file) so other apps can leverage this
1899 self.logger.info("Uploading on backend at {}".format(
1900 myslice_dict['backend']))
1901 uploader = ManifoldUploader(logger=self.logger,
1902 url=myslice_dict['backend'],
1903 platform=myslice_dict['platform'],
1904 username=myslice_dict['username'],
1905 password=options.password)
1906 uploader.prompt_all()
1907 (count_all, count_success) = (0, 0)
1908 for (hrn, htype, delegated_credential, filename) in hrn_delegated_credentials:
1910 inspect = Credential(string=delegated_credential)
1911 expire_datetime = inspect.get_expiration()
1912 message = "{} ({}) [exp:{}]".format(hrn, htype, expire_datetime)
1913 if uploader.upload(delegated_credential, message=message):
1916 self.logger.info("Successfully uploaded {}/{} credentials"
1917 .format(count_success, count_all))
1919 # at first I thought we would want to save these,
1920 # like 'sfi delegate does' but on second thought
1921 # it is probably not helpful as people would not
1922 # need to run 'sfi delegate' at all anymore
1923 if count_success != count_all:
1925 # xxx should analyze result
1928 @declare_command("cred", "")
1929 def trusted(self, options, args):
1931 return the trusted certs at this interface (get_trusted_certs)
1933 if options.registry_interface:
1934 server = self.registry()
1936 server = self.sliceapi()
1937 cred = self.my_authority_credential_string()
1938 trusted_certs = server.get_trusted_certs(cred)
1939 if not options.registry_interface:
1940 trusted_certs = ReturnValue.get_value(trusted_certs)
1942 for trusted_cert in trusted_certs:
1943 print("\n===========================================================\n")
1944 gid = GID(string=trusted_cert)
1946 cert = Certificate(string=trusted_cert)
1947 self.logger.debug('Sfi.trusted -> {}'.format(cert.get_subject()))
1948 print("Certificate:\n{}\n\n".format(trusted_cert))
1949 # xxx should analyze result
1952 @declare_command("", "")
1953 def introspect(self, options, args):
1955 If remote server supports XML-RPC instrospection API, allows
1956 to list supported methods
1958 if options.registry_interface:
1959 server = self.registry()
1961 server = self.sliceapi()
1962 results = server.serverproxy.system.listMethods()
1963 # at first sight a list here means it's fine,
1964 # and a dict suggests an error (no support for introspection?)
1965 if isinstance(results, list):
1966 results = [name for name in results if 'system.' not in name]
1968 print("== methods supported at {}".format(server.url))
1969 if 'Discover' in results:
1970 print("== has support for 'Discover' - most likely a v3")
1972 print("== has no support for 'Discover' - most likely a v2")
1973 for name in results:
1976 print("Got return of type {}, expected a list".format(type(results)))
1977 print("This suggests the remote end does not support introspection")