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 sfi_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
349 self.logger = sfi_logger
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)
609 self.sfi_parser = self.create_parser_global()
610 (options, args) = self.sfi_parser.parse_args()
612 self.print_commands_help(options)
614 self.options = options
616 self.logger.setLevelFromOptVerbose(self.options.verbose)
619 self.logger.critical("No command given. Use -h for help.")
620 self.print_commands_help(options)
623 # complete / find unique match with command set
624 command_candidates = Candidates(commands_list)
626 command = command_candidates.only_match(input)
628 self.print_commands_help(options)
630 # second pass options parsing
631 self.command = command
632 self.command_parser = self.create_parser_command(command)
633 (command_options, command_args) = self.command_parser.parse_args(
635 if command_options.help:
638 self.command_options = command_options
640 # allow incoming types on 2 characters only
641 if hasattr(command_options, 'type'):
642 command_options.type = normalize_type(command_options.type)
643 if not command_options.type:
648 self.logger.debug("Command={}".format(self.command))
651 retcod = self.dispatch(command, command_options, command_args)
655 self.logger.log_exc("sfi command {} failed".format(command))
660 def read_config(self):
661 config_file = os.path.join(self.options.sfi_dir, "sfi_config")
662 shell_config_file = os.path.join(self.options.sfi_dir, "sfi_config.sh")
664 if Config.is_ini(config_file):
665 config = Config(config_file)
667 # try upgrading from shell config format
668 fp, fn = mkstemp(suffix='sfi_config', text=True)
670 # we need to preload the sections we want parsed
671 # from the shell config
672 config.add_section('sfi')
673 # sface users should be able to use this same file to configure
675 config.add_section('sface')
676 # manifold users should be able to specify the details
677 # of their backend server here for 'sfi myslice'
678 config.add_section('myslice')
679 config.load(config_file)
681 shutil.move(config_file, shell_config_file)
683 config.save(config_file)
686 self.logger.critical(
687 "Failed to read configuration file {}".format(config_file))
689 "Make sure to remove the export clauses and to add quotes")
690 if self.options.verbose == 0:
691 self.logger.info("Re-run with -v for more details")
694 "Could not read config file {}".format(config_file))
697 self.config_instance = config
700 if (self.options.sm is not None):
701 self.sm_url = self.options.sm
702 elif hasattr(config, "SFI_SM"):
703 self.sm_url = config.SFI_SM
706 "You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in {}".format(config_file))
710 if (self.options.registry is not None):
711 self.reg_url = self.options.registry
712 elif hasattr(config, "SFI_REGISTRY"):
713 self.reg_url = config.SFI_REGISTRY
716 "You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in {}".format(config_file))
720 if (self.options.user is not None):
721 self.user = self.options.user
722 elif hasattr(config, "SFI_USER"):
723 self.user = config.SFI_USER
726 "You need to set e.g. SFI_USER='plc.princeton.username' in {}".format(config_file))
730 if (self.options.auth is not None):
731 self.authority = self.options.auth
732 elif hasattr(config, "SFI_AUTH"):
733 self.authority = config.SFI_AUTH
736 "You need to set e.g. SFI_AUTH='plc.princeton' in {}".format(config_file))
739 self.config_file = config_file
744 # Get various credential and spec files
746 # Establishes limiting conventions
747 # - conflates MAs and SAs
748 # - assumes last token in slice name is unique
750 # Bootstraps credentials
751 # - bootstrap user credential from self-signed certificate
752 # - bootstrap authority credential from user credential
753 # - bootstrap slice credential from user credential
756 # init self-signed cert, user credentials and gid
758 if self.options.verbose:
760 "Initializing SfaClientBootstrap with {}".format(self.reg_url))
761 client_bootstrap = SfaClientBootstrap(self.user, self.reg_url, self.options.sfi_dir,
763 # if -k is provided, use this to initialize private key
764 if self.options.user_private_key:
765 client_bootstrap.init_private_key_if_missing(
766 self.options.user_private_key)
768 # trigger legacy compat code if needed
769 # the name has changed from just <leaf>.pkey to <hrn>.pkey
770 if not os.path.isfile(client_bootstrap.private_key_filename()):
771 self.logger.info("private key not found, trying legacy name")
773 legacy_private_key = os.path.join(self.options.sfi_dir, "{}.pkey"
774 .format(Xrn.unescape(get_leaf(self.user))))
775 self.logger.debug("legacy_private_key={}"
776 .format(legacy_private_key))
777 client_bootstrap.init_private_key_if_missing(
779 self.logger.info("Copied private key from legacy location {}"
780 .format(legacy_private_key))
782 self.logger.log_exc("Can't find private key ")
786 client_bootstrap.bootstrap_my_gid()
787 # extract what's needed
788 self.private_key = client_bootstrap.private_key()
789 self.my_credential_string = client_bootstrap.my_credential_string()
790 self.my_credential = {'geni_type': 'geni_sfa',
792 'geni_value': self.my_credential_string}
793 self.my_gid = client_bootstrap.my_gid()
794 self.client_bootstrap = client_bootstrap
796 def my_authority_credential_string(self):
797 if not self.authority:
798 self.logger.critical(
799 "no authority specified. Use -a or set SF_AUTH")
801 return self.client_bootstrap.authority_credential_string(self.authority)
803 def authority_credential_string(self, auth_hrn):
804 return self.client_bootstrap.authority_credential_string(auth_hrn)
806 def slice_credential_string(self, name):
807 return self.client_bootstrap.slice_credential_string(name)
809 def slice_credential(self, name):
810 return {'geni_type': 'geni_sfa',
812 'geni_value': self.slice_credential_string(name)}
814 # xxx should be supported by sfaclientbootstrap as well
815 def delegate_cred(self, object_cred, hrn, type='authority'):
816 # the gid and hrn of the object we are delegating
817 if isinstance(object_cred, str):
818 object_cred = Credential(string=object_cred)
819 object_gid = object_cred.get_gid_object()
820 object_hrn = object_gid.get_hrn()
822 if not object_cred.get_privileges().get_all_delegate():
823 self.logger.error("Object credential {} does not have delegate bit set"
827 # the delegating user's gid
828 caller_gidfile = self.my_gid()
830 # the gid of the user who will be delegated to
831 delegee_gid = self.client_bootstrap.gid(hrn, type)
832 delegee_hrn = delegee_gid.get_hrn()
833 dcred = object_cred.delegate(
834 delegee_gid, self.private_key, caller_gidfile)
835 return dcred.save_to_string(save_parents=True)
838 # Management of the servers
843 if not hasattr(self, 'registry_proxy'):
844 self.logger.info("Contacting Registry at: {}".format(self.reg_url))
845 self.registry_proxy \
846 = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
847 timeout=self.options.timeout, verbose=self.options.debug)
848 return self.registry_proxy
852 if not hasattr(self, 'sliceapi_proxy'):
853 # if the command exposes the --component option, figure it's
854 # hostname and connect at CM_PORT
855 if hasattr(self.command_options, 'component') and self.command_options.component:
856 # resolve the hrn at the registry
857 node_hrn = self.command_options.component
858 records = self.registry().Resolve(node_hrn, self.my_credential_string)
859 records = filter_records('node', records)
862 "No such component:{}".format(opts.component))
864 cm_url = "http://{}:{}/".format(record['hostname'], CM_PORT)
865 self.sliceapi_proxy = SfaServerProxy(
866 cm_url, self.private_key, self.my_gid)
868 # otherwise use what was provided as --sliceapi, or SFI_SM in
870 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
871 self.sm_url = 'http://' + self.sm_url
873 "Contacting Slice Manager at: {}".format(self.sm_url))
874 self.sliceapi_proxy \
875 = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
876 timeout=self.options.timeout, verbose=self.options.debug)
877 return self.sliceapi_proxy
879 def get_cached_server_version(self, server):
880 # check local cache first
883 cache_file = os.path.join(self.options.sfi_dir, 'sfi_cache.dat')
884 cache_key = server.url + "-version"
886 cache = Cache(cache_file)
889 self.logger.info("Local cache not found at: {}".format(cache_file))
892 version = cache.get(cache_key)
895 result = server.GetVersion()
896 version = ReturnValue.get_value(result)
897 # cache version for 20 minutes
898 cache.add(cache_key, version, ttl=60 * 20)
899 self.logger.info("Updating cache file {}".format(cache_file))
900 cache.save_to_file(cache_file)
904 # resurrect this temporarily so we can support V1 aggregates for a while
905 def server_supports_options_arg(self, server):
907 Returns true if server support the optional call_id arg, false otherwise.
909 server_version = self.get_cached_server_version(server)
911 # xxx need to rewrite this
912 if int(server_version.get('geni_api')) >= 2:
916 def server_supports_call_id_arg(self, server):
917 server_version = self.get_cached_server_version(server)
919 if 'sfa' in server_version and 'code_tag' in server_version:
920 code_tag = server_version['code_tag']
921 code_tag_parts = code_tag.split("-")
922 version_parts = code_tag_parts[0].split(".")
923 major, minor = version_parts[0], version_parts[1]
924 rev = code_tag_parts[1]
925 if int(major) == 1 and minor == 0 and build >= 22:
929 # ois = options if supported
930 # to be used in something like serverproxy.Method(arg1, arg2,
931 # *self.ois(api_options))
932 def ois(self, server, option_dict):
933 if self.server_supports_options_arg(server):
935 elif self.server_supports_call_id_arg(server):
936 return [unique_call_id()]
940 # cis = call_id if supported - like ois
941 def cis(self, server):
942 if self.server_supports_call_id_arg(server):
943 return [unique_call_id]
948 def get_rspec_file(self, rspec):
949 if (os.path.isabs(rspec)):
952 file = os.path.join(self.options.sfi_dir, rspec)
953 if (os.path.isfile(file)):
956 self.logger.critical("No such rspec file {}".format(rspec))
959 def get_record_file(self, record):
960 if (os.path.isabs(record)):
963 file = os.path.join(self.options.sfi_dir, record)
964 if (os.path.isfile(file)):
967 self.logger.critical(
968 "No such registry record file {}".format(record))
971 # helper function to analyze raw output
972 # for main : return 0 if everything is fine, something else otherwise
974 def success(self, raw):
975 return_value = ReturnValue(raw)
976 output = ReturnValue.get_output(return_value)
977 # means everything is fine
980 # something went wrong
981 print('ERROR:', output)
984 #==========================================================================
985 # Following functions implement the commands
987 # Registry-related commands
988 #==========================================================================
990 @declare_command("", "")
991 def config(self, options, args):
993 Display contents of current config
999 print("# From configuration file {}".format(self.config_file))
1000 flags = [('sfi', [('registry', 'reg_url'),
1001 ('auth', 'authority'),
1008 ('myslice', ['backend', 'delegate', 'platform', 'username']))
1010 for (section, tuples) in flags:
1011 print("[{}]".format(section))
1013 for external_name, internal_name in tuples:
1014 print("{:<20} = {}".format(
1015 external_name, getattr(self, internal_name)))
1017 for external_name, internal_name in tuples:
1018 varname = "{}_{}".format(
1019 section.upper(), external_name.upper())
1020 value = getattr(self.config_instance, varname)
1021 print("{:<20} = {}".format(external_name, value))
1022 # xxx should analyze result
1025 @declare_command("", "")
1026 def version(self, options, args):
1028 display an SFA server version (GetVersion)
1029 or version information about sfi itself
1035 if options.version_local:
1036 version = version_core()
1038 if options.registry_interface:
1039 server = self.registry()
1041 server = self.sliceapi()
1042 result = server.GetVersion()
1043 version = ReturnValue.get_value(result)
1044 if self.options.raw:
1045 save_raw_to_file(result, self.options.raw,
1046 self.options.rawformat, self.options.rawbanner)
1048 pprinter = PrettyPrinter(indent=4)
1049 pprinter.pprint(version)
1050 # xxx should analyze result
1053 @declare_command("authority", "")
1054 def list(self, options, args):
1056 list entries in named authority registry (List)
1064 if options.recursive:
1065 opts['recursive'] = options.recursive
1067 if options.show_credential:
1068 show_credentials(self.my_credential_string)
1070 list = self.registry().List(hrn, self.my_credential_string, options)
1072 raise Exception("Not enough parameters for the 'list' command")
1074 # filter on person, slice, site, node, etc.
1075 # This really should be in the self.filter_records funct def comment...
1076 list = filter_records(options.type, list)
1077 terminal_render(list, options)
1079 save_records_to_file(options.file, list, options.fileformat)
1080 # xxx should analyze result
1083 @declare_command("name", "")
1084 def show(self, options, args):
1086 show details about named registry record (Resolve)
1093 # explicitly require Resolve to run in details mode
1094 resolve_options = {}
1095 if not options.no_details:
1096 resolve_options['details'] = True
1097 record_dicts = self.registry().Resolve(
1098 hrn, self.my_credential_string, resolve_options)
1099 record_dicts = filter_records(options.type, record_dicts)
1100 if not record_dicts:
1101 self.logger.error("No record of type {}".format(options.type))
1103 # user has required to focus on some keys
1105 def project(record):
1107 for key in options.keys:
1109 projected[key] = record[key]
1113 record_dicts = [project(record) for record in record_dicts]
1114 records = [Record(dict=record_dict) for record_dict in record_dicts]
1115 for record in records:
1116 if (options.format == "text"):
1117 record.dump(sort=True)
1119 print(record.save_as_xml())
1121 save_records_to_file(
1122 options.file, record_dicts, options.fileformat)
1123 # xxx should analyze result
1126 # this historically was named 'add', it is now 'register' with an alias
1128 @declare_command("[xml-filename]", "", ['add'])
1129 def register(self, options, args):
1131 create new record in registry (Register)
1132 from command line options (recommended)
1133 old-school method involving an xml file still supported
1139 auth_cred = self.my_authority_credential_string()
1140 if options.show_credential:
1141 show_credentials(auth_cred)
1145 record_filepath = args[0]
1146 rec_file = self.get_record_file(record_filepath)
1147 record_dict.update(load_record_from_file(
1148 rec_file).record_to_dict())
1150 print("Cannot load record file {}".format(record_filepath))
1153 record_dict.update(load_record_from_opts(options).record_to_dict())
1154 # we should have a type by now
1155 if 'type' not in record_dict:
1158 # this is still planetlab dependent.. as plc will whine without that
1159 # also, it's only for adding
1160 if record_dict['type'] == 'user':
1161 if not 'first_name' in record_dict:
1162 record_dict['first_name'] = record_dict['hrn']
1163 if 'last_name' not in record_dict:
1164 record_dict['last_name'] = record_dict['hrn']
1165 register = self.registry().Register(record_dict, auth_cred)
1166 # xxx looks like the result here is not ReturnValue-compatible
1167 # return self.success (register)
1168 # xxx should analyze result
1171 @declare_command("[xml-filename]", "")
1172 def update(self, options, args):
1174 update record into registry (Update)
1175 from command line options (recommended)
1176 old-school method involving an xml file still supported
1184 record_filepath = args[0]
1185 rec_file = self.get_record_file(record_filepath)
1186 record_dict.update(load_record_from_file(
1187 rec_file).record_to_dict())
1189 record_dict.update(load_record_from_opts(options).record_to_dict())
1190 # at the very least we need 'type' here
1191 if 'type' not in record_dict or record_dict['type'] is None:
1195 # don't translate into an object, as this would possibly distort
1196 # user-provided data; e.g. add an 'email' field to Users
1197 if record_dict['type'] in ['user']:
1198 if record_dict['hrn'] == self.user:
1199 cred = self.my_credential_string
1201 cred = self.my_authority_credential_string()
1202 elif record_dict['type'] in ['slice']:
1204 cred = self.slice_credential_string(record_dict['hrn'])
1205 except ServerException as e:
1206 # XXX smbaker -- once we have better error return codes, update this
1207 # to do something better than a string compare
1208 if "Permission error" in e.args[0]:
1209 cred = self.my_authority_credential_string()
1212 elif record_dict['type'] in ['authority']:
1213 cred = self.my_authority_credential_string()
1214 elif record_dict['type'] in ['node']:
1215 cred = self.my_authority_credential_string()
1218 "unknown record type {}".format(record_dict['type']))
1219 if options.show_credential:
1220 show_credentials(cred)
1221 update = self.registry().Update(record_dict, cred)
1222 # xxx looks like the result here is not ReturnValue-compatible
1223 # return self.success(update)
1224 # xxx should analyze result
1227 @declare_command("hrn", "")
1228 def remove(self, options, args):
1230 remove registry record by name (Remove)
1232 auth_cred = self.my_authority_credential_string()
1241 if options.show_credential:
1242 show_credentials(auth_cred)
1243 remove = self.registry().Remove(hrn, auth_cred, type)
1244 # xxx looks like the result here is not ReturnValue-compatible
1245 # return self.success (remove)
1246 # xxx should analyze result
1249 # ==================================================================
1250 # Slice-related commands
1251 # ==================================================================
1253 # show rspec for named slice
1254 @declare_command("", "", ['discover'])
1255 def resources(self, options, args):
1257 discover available resources (ListResources)
1263 server = self.sliceapi()
1265 creds = [self.my_credential_string]
1266 if options.delegate:
1267 creds.append(self.delegate_cred(
1268 cred, get_authority(self.authority)))
1269 if options.show_credential:
1270 show_credentials(creds)
1272 # no need to check if server accepts the options argument since the options has
1273 # been a required argument since v1 API
1275 # always send call_id to v2 servers
1276 api_options['call_id'] = unique_call_id()
1277 # ask for cached value if available
1278 api_options['cached'] = True
1280 api_options['info'] = options.info
1281 if options.list_leases:
1282 api_options['list_leases'] = options.list_leases
1284 if options.current == True:
1285 api_options['cached'] = False
1287 api_options['cached'] = True
1288 version_manager = VersionManager()
1289 api_options['geni_rspec_version'] = version_manager.get_version(
1290 options.rspec_version).to_dict()
1292 list_resources = server.ListResources(creds, api_options)
1293 value = ReturnValue.get_value(list_resources)
1294 if self.options.raw:
1295 save_raw_to_file(list_resources, self.options.raw,
1296 self.options.rawformat, self.options.rawbanner)
1297 if options.file is not None:
1298 save_rspec_to_file(value, options.file)
1299 if (self.options.raw is None) and (options.file is None):
1300 display_rspec(value, options.format)
1301 return self.success(list_resources)
1303 @declare_command("slice_hrn", "")
1304 def describe(self, options, args):
1306 shows currently allocated/provisioned resources
1307 of the named slice or set of slivers (Describe)
1313 server = self.sliceapi()
1315 creds = [self.slice_credential(args[0])]
1316 if options.delegate:
1317 creds.append(self.delegate_cred(
1318 cred, get_authority(self.authority)))
1319 if options.show_credential:
1320 show_credentials(creds)
1322 api_options = {'call_id': unique_call_id(),
1324 'info': options.info,
1325 'list_leases': options.list_leases,
1326 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1329 api_options['info'] = options.info
1331 if options.rspec_version:
1332 version_manager = VersionManager()
1333 server_version = self.get_cached_server_version(server)
1334 if 'sfa' in server_version:
1335 # just request the version the client wants
1336 api_options['geni_rspec_version'] = version_manager.get_version(
1337 options.rspec_version).to_dict()
1339 api_options['geni_rspec_version'] = {
1340 'type': 'geni', 'version': '3'}
1341 urn = Xrn(args[0], type='slice').get_urn()
1342 remove_none_fields(api_options)
1343 describe = server.Describe([urn], creds, api_options)
1344 value = ReturnValue.get_value(describe)
1345 if self.options.raw:
1346 save_raw_to_file(describe, self.options.raw,
1347 self.options.rawformat, self.options.rawbanner)
1348 if options.file is not None:
1349 save_rspec_to_file(value['geni_rspec'], options.file)
1350 if (self.options.raw is None) and (options.file is None):
1351 display_rspec(value['geni_rspec'], options.format)
1352 return self.success(describe)
1354 @declare_command("slice_hrn [<sliver_urn>...]", "")
1355 def delete(self, options, args):
1357 de-allocate and de-provision all or named slivers of the named slice (Delete)
1363 server = self.sliceapi()
1366 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1369 # we have sliver urns
1370 sliver_urns = args[1:]
1372 # we provision all the slivers of the slice
1373 sliver_urns = [slice_urn]
1376 slice_cred = self.slice_credential(slice_hrn)
1377 creds = [slice_cred]
1379 # options and call_id when supported
1381 api_options['call_id'] = unique_call_id()
1382 if options.show_credential:
1383 show_credentials(creds)
1384 delete = server.Delete(sliver_urns, creds, *
1385 self.ois(server, api_options))
1386 value = ReturnValue.get_value(delete)
1387 if self.options.raw:
1388 save_raw_to_file(delete, self.options.raw,
1389 self.options.rawformat, self.options.rawbanner)
1392 return self.success(delete)
1394 @declare_command("slice_hrn rspec", "")
1395 def allocate(self, options, args):
1397 allocate resources to the named slice (Allocate)
1403 server = self.sliceapi()
1404 server_version = self.get_cached_server_version(server)
1406 rspec_file = self.get_rspec_file(args[1])
1408 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1411 creds = [self.slice_credential(slice_hrn)]
1413 delegated_cred = None
1414 if server_version.get('interface') == 'slicemgr':
1415 # delegate our cred to the slice manager
1416 # do not delegate cred to slicemgr...not working at the moment
1418 # if server_version.get('hrn'):
1419 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1420 # elif server_version.get('urn'):
1421 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1423 if options.show_credential:
1424 show_credentials(creds)
1428 api_options['call_id'] = unique_call_id()
1432 slice_records = self.registry().Resolve(
1433 slice_urn, [self.my_credential_string])
1434 remove_none_fields(slice_records[0])
1435 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers'] != []:
1436 slice_record = slice_records[0]
1437 user_hrns = slice_record['reg-researchers']
1438 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1439 user_records = self.registry().Resolve(
1440 user_urns, [self.my_credential_string])
1441 sfa_users = sfa_users_arg(user_records, slice_record)
1442 geni_users = pg_users_arg(user_records)
1444 api_options['sfa_users'] = sfa_users
1445 api_options['geni_users'] = geni_users
1447 with open(rspec_file) as rspec:
1448 rspec_xml = rspec.read()
1449 allocate = server.Allocate(
1450 slice_urn, creds, rspec_xml, api_options)
1451 value = ReturnValue.get_value(allocate)
1452 if self.options.raw:
1453 save_raw_to_file(allocate, self.options.raw,
1454 self.options.rawformat, self.options.rawbanner)
1455 if options.file is not None:
1456 save_rspec_to_file(value['geni_rspec'], options.file)
1457 if (self.options.raw is None) and (options.file is None):
1459 return self.success(allocate)
1461 @declare_command("slice_hrn [<sliver_urn>...]", "")
1462 def provision(self, options, args):
1464 provision all or named already allocated slivers of the named slice (Provision)
1470 server = self.sliceapi()
1471 server_version = self.get_cached_server_version(server)
1473 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1475 # we have sliver urns
1476 sliver_urns = args[1:]
1478 # we provision all the slivers of the slice
1479 sliver_urns = [slice_urn]
1482 creds = [self.slice_credential(slice_hrn)]
1483 delegated_cred = None
1484 if server_version.get('interface') == 'slicemgr':
1485 # delegate our cred to the slice manager
1486 # do not delegate cred to slicemgr...not working at the moment
1488 # if server_version.get('hrn'):
1489 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1490 # elif server_version.get('urn'):
1491 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1493 if options.show_credential:
1494 show_credentials(creds)
1497 api_options['call_id'] = unique_call_id()
1499 # set the requtested rspec version
1500 version_manager = VersionManager()
1501 rspec_version = version_manager._get_version('geni', '3').to_dict()
1502 api_options['geni_rspec_version'] = rspec_version
1505 # need to pass along user keys to the aggregate.
1507 # { urn: urn:publicid:IDN+emulab.net+user+alice
1508 # keys: [<ssh key A>, <ssh key B>]
1511 slice_records = self.registry().Resolve(
1512 slice_urn, [self.my_credential_string])
1513 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers'] != []:
1514 slice_record = slice_records[0]
1515 user_hrns = slice_record['reg-researchers']
1516 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1517 user_records = self.registry().Resolve(
1518 user_urns, [self.my_credential_string])
1519 users = pg_users_arg(user_records)
1521 api_options['geni_users'] = users
1522 provision = server.Provision(sliver_urns, creds, api_options)
1523 value = ReturnValue.get_value(provision)
1524 if self.options.raw:
1525 save_raw_to_file(provision, self.options.raw,
1526 self.options.rawformat, self.options.rawbanner)
1527 if options.file is not None:
1528 save_rspec_to_file(value['geni_rspec'], options.file)
1529 if (self.options.raw is None) and (options.file is None):
1531 return self.success(provision)
1533 @declare_command("slice_hrn", "")
1534 def status(self, options, args):
1536 retrieve the status of the slivers belonging to the named slice (Status)
1542 server = self.sliceapi()
1545 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1548 slice_cred = self.slice_credential(slice_hrn)
1549 creds = [slice_cred]
1551 # options and call_id when supported
1553 api_options['call_id'] = unique_call_id()
1554 if options.show_credential:
1555 show_credentials(creds)
1556 status = server.Status([slice_urn], creds, *
1557 self.ois(server, api_options))
1558 value = ReturnValue.get_value(status)
1559 if self.options.raw:
1560 save_raw_to_file(status, self.options.raw,
1561 self.options.rawformat, self.options.rawbanner)
1564 return self.success(status)
1566 @declare_command("slice_hrn [<sliver_urn>...] action", "")
1567 def action(self, options, args):
1569 Perform the named operational action on all or named slivers of the named slice
1575 server = self.sliceapi()
1579 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1581 # we have sliver urns
1582 sliver_urns = args[1:-1]
1584 # we provision all the slivers of the slice
1585 sliver_urns = [slice_urn]
1588 slice_cred = self.slice_credential(args[0])
1589 creds = [slice_cred]
1590 if options.delegate:
1591 delegated_cred = self.delegate_cred(
1592 slice_cred, get_authority(self.authority))
1593 creds.append(delegated_cred)
1595 perform_action = server.PerformOperationalAction(
1596 sliver_urns, creds, action, api_options)
1597 value = ReturnValue.get_value(perform_action)
1598 if self.options.raw:
1599 save_raw_to_file(perform_action, self.options.raw,
1600 self.options.rawformat, self.options.rawbanner)
1603 return self.success(perform_action)
1605 @declare_command("slice_hrn [<sliver_urn>...] time",
1606 "\n".join(["sfi renew onelab.ple.heartbeat 2015-04-31",
1607 "sfi renew onelab.ple.heartbeat 2015-04-31T14:00:00Z",
1608 "sfi renew onelab.ple.heartbeat +5d",
1609 "sfi renew onelab.ple.heartbeat +3w",
1610 "sfi renew onelab.ple.heartbeat +2m", ]))
1611 def renew(self, options, args):
1619 server = self.sliceapi()
1621 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1624 # we have sliver urns
1625 sliver_urns = args[1:-1]
1627 # we provision all the slivers of the slice
1628 sliver_urns = [slice_urn]
1629 input_time = args[-1]
1631 # time: don't try to be smart on the time format, server-side will
1633 slice_cred = self.slice_credential(args[0])
1634 creds = [slice_cred]
1635 # options and call_id when supported
1637 api_options['call_id'] = unique_call_id()
1639 api_options['geni_extend_alap'] = True
1640 if options.show_credential:
1641 show_credentials(creds)
1642 renew = server.Renew(sliver_urns, creds, input_time,
1643 *self.ois(server, api_options))
1644 value = ReturnValue.get_value(renew)
1645 if self.options.raw:
1646 save_raw_to_file(renew, self.options.raw,
1647 self.options.rawformat, self.options.rawbanner)
1650 return self.success(renew)
1652 @declare_command("slice_hrn", "")
1653 def shutdown(self, options, args):
1655 shutdown named slice (Shutdown)
1661 server = self.sliceapi()
1664 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1666 slice_cred = self.slice_credential(slice_hrn)
1667 creds = [slice_cred]
1668 shutdown = server.Shutdown(slice_urn, creds)
1669 value = ReturnValue.get_value(shutdown)
1670 if self.options.raw:
1671 save_raw_to_file(shutdown, self.options.raw,
1672 self.options.rawformat, self.options.rawbanner)
1675 return self.success(shutdown)
1677 @declare_command("[name]", "")
1678 def gid(self, options, args):
1680 Create a GID (CreateGid)
1686 target_hrn = args[0]
1687 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1688 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1690 filename = options.file
1692 filename = os.sep.join(
1693 [self.options.sfi_dir, '{}.gid'.format(target_hrn)])
1694 self.logger.info("writing {} gid to {}".format(target_hrn, filename))
1695 GID(string=gid).save_to_file(filename)
1696 # xxx should analyze result
1699 ####################
1700 @declare_command("to_hrn", """$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1702 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1703 the set of credentials in the scope for this call would be
1704 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1706 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1708 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1709 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1710 because of the two -s options
1713 def delegate(self, options, args):
1715 (locally) create delegate credential for use by given hrn
1716 make sure to check for 'sfi myslice' instead if you plan
1724 # support for several delegations in the same call
1725 # so first we gather the things to do
1727 for slice_hrn in options.delegate_slices:
1728 message = "{}.slice".format(slice_hrn)
1729 original = self.slice_credential_string(slice_hrn)
1730 tuples.append((message, original,))
1731 if options.delegate_pi:
1732 my_authority = self.authority
1733 message = "{}.pi".format(my_authority)
1734 original = self.my_authority_credential_string()
1735 tuples.append((message, original,))
1736 for auth_hrn in options.delegate_auths:
1737 message = "{}.auth".format(auth_hrn)
1738 original = self.authority_credential_string(auth_hrn)
1739 tuples.append((message, original, ))
1740 # if nothing was specified at all at this point, let's assume -u
1742 options.delegate_user = True
1744 if options.delegate_user:
1745 message = "{}.user".format(self.user)
1746 original = self.my_credential_string
1747 tuples.append((message, original, ))
1749 # default type for beneficial is user unless -A
1750 to_type = 'authority' if options.delegate_to_authority else 'user'
1752 # let's now handle all this
1753 # it's all in the filenaming scheme
1754 for (message, original) in tuples:
1755 delegated_string = self.client_bootstrap.delegate_credential_string(
1756 original, to_hrn, to_type)
1757 delegated_credential = Credential(string=delegated_string)
1758 filename = os.path.join(self.options.sfi_dir,
1759 "{}_for_{}.{}.cred".format(message, to_hrn, to_type))
1760 delegated_credential.save_to_file(filename, save_parents=True)
1761 self.logger.info("delegated credential for {} to {} and wrote to {}"
1762 .format(message, to_hrn, filename))
1764 ####################
1765 @declare_command("", """$ less +/myslice sfi_config
1767 backend = http://manifold.pl.sophia.inria.fr:7080
1768 # the HRN that myslice uses, so that we are delegating to
1769 delegate = ple.upmc.slicebrowser
1770 # platform - this is a myslice concept
1772 # username - as of this writing (May 2013) a simple login name
1776 will first collect the slices that you are part of, then make sure
1777 all your credentials are up-to-date (read: refresh expired ones)
1778 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1779 and upload them all on myslice backend, using 'platform' and 'user'.
1780 A password will be prompted for the upload part.
1782 $ sfi -v myslice -- or sfi -vv myslice
1783 same but with more and more verbosity
1785 $ sfi m -b http://mymanifold.foo.com:7080/
1786 is synonym to sfi myslice as no other command starts with an 'm'
1787 and uses a custom backend for this one call
1790 def myslice(self, options, args):
1791 """ This helper is for refreshing your credentials at myslice; it will
1792 * compute all the slices that you currently have credentials on
1793 * refresh all your credentials (you as a user and pi, your slices)
1794 * upload them to the manifold backend server
1795 for last phase, sfi_config is read to look for the [myslice] section,
1796 and namely the 'backend', 'delegate' and 'user' settings
1803 # enable info by default
1804 self.logger.setLevelFromOptVerbose(self.options.verbose + 1)
1805 # the rough sketch goes like this
1806 # (0) produce a p12 file
1807 self.client_bootstrap.my_pkcs12()
1809 # (a) rain check for sufficient config in sfi_config
1811 myslice_keys = ['backend', 'delegate', 'platform', 'username']
1812 for key in myslice_keys:
1814 # oct 2013 - I'm finding myself juggling with config files
1815 # so a couple of command-line options can now override config
1816 if hasattr(options, key) and getattr(options, key) is not None:
1817 value = getattr(options, key)
1819 full_key = "MYSLICE_" + key.upper()
1820 value = getattr(self.config_instance, full_key, None)
1822 myslice_dict[key] = value
1824 print("Unsufficient config, missing key {} in [myslice] section of sfi_config"
1826 if len(myslice_dict) != len(myslice_keys):
1829 # (b) figure whether we are PI for the authority where we belong
1830 self.logger.info("Resolving our own id {}".format(self.user))
1831 my_records = self.registry().Resolve(self.user, self.my_credential_string)
1832 if len(my_records) != 1:
1833 print("Cannot Resolve {} -- exiting".format(self.user))
1835 my_record = my_records[0]
1836 my_auths_all = my_record['reg-pi-authorities']
1838 "Found {} authorities that we are PI for".format(len(my_auths_all)))
1839 self.logger.debug("They are {}".format(my_auths_all))
1841 my_auths = my_auths_all
1842 if options.delegate_auths:
1843 my_auths = list(set(my_auths_all).intersection(
1844 set(options.delegate_auths)))
1846 "Restricted to user-provided auths {}".format(my_auths))
1848 # (c) get the set of slices that we are in
1849 my_slices_all = my_record['reg-slices']
1851 "Found {} slices that we are member of".format(len(my_slices_all)))
1852 self.logger.debug("They are: {}".format(my_slices_all))
1854 my_slices = my_slices_all
1855 # if user provided slices, deal only with these - if they are found
1856 if options.delegate_slices:
1857 my_slices = list(set(my_slices_all).intersection(
1858 set(options.delegate_slices)))
1860 "Restricted to user-provided slices: {}".format(my_slices))
1862 # (d) make sure we have *valid* credentials for all these
1863 hrn_credentials = []
1864 hrn_credentials.append((self.user, 'user', self.my_credential_string,))
1865 for auth_hrn in my_auths:
1866 hrn_credentials.append(
1867 (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),))
1868 for slice_hrn in my_slices:
1870 hrn_credentials.append(
1871 (slice_hrn, 'slice', self.slice_credential_string(slice_hrn),))
1873 print("WARNING: could not get slice credential for slice {}"
1876 # (e) check for the delegated version of these
1877 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1878 # switch to myslice using an authority instead of a user
1879 delegatee_type = 'user'
1880 delegatee_hrn = myslice_dict['delegate']
1881 hrn_delegated_credentials = []
1882 for (hrn, htype, credential) in hrn_credentials:
1883 delegated_credential = self.client_bootstrap.delegate_credential_string(
1884 credential, delegatee_hrn, delegatee_type)
1885 # save these so user can monitor what she's uploaded
1886 filename = os.path.join(self.options.sfi_dir,
1887 "{}.{}_for_{}.{}.cred"
1888 .format(hrn, htype, delegatee_hrn, delegatee_type))
1889 with open(filename, 'w') as f:
1890 f.write(delegated_credential)
1891 self.logger.debug("(Over)wrote {}".format(filename))
1892 hrn_delegated_credentials.append(
1893 (hrn, htype, delegated_credential, filename, ))
1895 # (f) and finally upload them to manifold server
1896 # xxx todo add an option so the password can be set on the command line
1897 # (but *NOT* in the config file) so other apps can leverage this
1898 self.logger.info("Uploading on backend at {}".format(
1899 myslice_dict['backend']))
1900 uploader = ManifoldUploader(logger=self.logger,
1901 url=myslice_dict['backend'],
1902 platform=myslice_dict['platform'],
1903 username=myslice_dict['username'],
1904 password=options.password)
1905 uploader.prompt_all()
1906 (count_all, count_success) = (0, 0)
1907 for (hrn, htype, delegated_credential, filename) in hrn_delegated_credentials:
1909 inspect = Credential(string=delegated_credential)
1910 expire_datetime = inspect.get_expiration()
1911 message = "{} ({}) [exp:{}]".format(hrn, htype, expire_datetime)
1912 if uploader.upload(delegated_credential, message=message):
1915 self.logger.info("Successfully uploaded {}/{} credentials"
1916 .format(count_success, count_all))
1918 # at first I thought we would want to save these,
1919 # like 'sfi delegate does' but on second thought
1920 # it is probably not helpful as people would not
1921 # need to run 'sfi delegate' at all anymore
1922 if count_success != count_all:
1924 # xxx should analyze result
1927 @declare_command("cred", "")
1928 def trusted(self, options, args):
1930 return the trusted certs at this interface (get_trusted_certs)
1932 if options.registry_interface:
1933 server = self.registry()
1935 server = self.sliceapi()
1936 cred = self.my_authority_credential_string()
1937 trusted_certs = server.get_trusted_certs(cred)
1938 if not options.registry_interface:
1939 trusted_certs = ReturnValue.get_value(trusted_certs)
1941 for trusted_cert in trusted_certs:
1942 print("\n===========================================================\n")
1943 gid = GID(string=trusted_cert)
1945 cert = Certificate(string=trusted_cert)
1946 self.logger.debug('Sfi.trusted -> {}'.format(cert.get_subject()))
1947 print("Certificate:\n{}\n\n".format(trusted_cert))
1948 # xxx should analyze result
1951 @declare_command("", "")
1952 def introspect(self, options, args):
1954 If remote server supports XML-RPC instrospection API, allows
1955 to list supported methods
1957 if options.registry_interface:
1958 server = self.registry()
1960 server = self.sliceapi()
1961 results = server.serverproxy.system.listMethods()
1962 # at first sight a list here means it's fine,
1963 # and a dict suggests an error (no support for introspection?)
1964 if isinstance(results, list):
1965 results = [name for name in results if 'system.' not in name]
1967 print("== methods supported at {}".format(server.url))
1968 if 'Discover' in results:
1969 print("== has support for 'Discover' - most likely a v3")
1971 print("== has no support for 'Discover' - most likely a v2")
1972 for name in results:
1975 print("Got return of type {}, expected a list".format(type(results)))
1976 print("This suggests the remote end does not support introspection")