2 # sfi.py - basic SFA command-line client
3 # this module is also used in sfascan
6 from __future__ import print_function
20 from lxml import etree
21 from optparse import OptionParser
22 from pprint import PrettyPrinter
23 from tempfile import mkstemp
25 from sfa.trust.certificate import Keypair, Certificate
26 from sfa.trust.gid import GID
27 from sfa.trust.credential import Credential
28 from sfa.trust.sfaticket import SfaTicket
30 from sfa.util.faults import SfaInvalidArgument
31 from sfa.util.sfalogging import sfi_logger
32 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
33 from sfa.util.config import Config
34 from sfa.util.version import version_core
35 from sfa.util.cache import Cache
36 from sfa.util.printable import printable
37 from sfa.util.py23 import StringIO
39 from sfa.storage.record import Record
41 from sfa.rspecs.rspec import RSpec
42 from sfa.rspecs.rspec_converter import RSpecConverter
43 from sfa.rspecs.version_manager import VersionManager
45 from sfa.client.sfaclientlib import SfaClientBootstrap
46 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
47 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
48 from sfa.client.return_value import ReturnValue
49 from sfa.client.candidates import Candidates
50 from sfa.client.manifolduploader import ManifoldUploader
53 DEFAULT_RSPEC_VERSION = "GENI 3"
55 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
56 terminal_render, filter_records
61 def display_rspec(rspec, format='rspec'):
63 tree = etree.parse(StringIO(rspec))
65 result = root.xpath("./network/site/node/hostname/text()")
66 elif format in ['ip']:
67 # The IP address is not yet part of the new RSpec
68 # so this doesn't do anything yet.
69 tree = etree.parse(StringIO(rspec))
71 result = root.xpath("./network/site/node/ipv4/text()")
79 def display_list(results):
80 for result in results:
84 def display_records(recordList, dump=False):
85 ''' Print all fields in the record'''
86 for record in recordList:
87 display_record(record, dump)
90 def display_record(record, dump=False):
92 record.dump(sort=True)
94 info = record.getdict()
95 print("{} ({})".format(info['hrn'], info['type']))
99 def filter_records(type, records):
100 filtered_records = []
101 for record in records:
102 if (record['type'] == type) or (type == "all"):
103 filtered_records.append(record)
104 return filtered_records
107 def credential_printable(cred):
108 credential = Credential(cred=cred)
110 result += credential.pretty_cred()
112 rights = credential.get_privileges()
113 result += "type={}\n".format(credential.type)
114 result += "version={}\n".format(credential.version)
115 result += "rights={}\n".format(rights)
119 def show_credentials(cred_s):
120 if not isinstance(cred_s, list):
123 print("Using Credential {}".format(credential_printable(cred)))
130 def save_raw_to_file(var, filename, format='text', banner=None):
132 _save_raw_to_file(var, sys.stdout, format, banner)
134 with open(filename, w) as fileobj:
135 _save_raw_to_file(var, fileobj, format, banner)
136 print("(Over)wrote {}".format(filename))
139 def _save_raw_to_file(var, f, format, banner):
142 f.write(banner + "\n")
143 f.write("{}".format(var))
145 f.write('\n' + banner + "\n")
146 elif format == "pickled":
147 f.write(pickle.dumps(var))
148 elif format == "json":
149 f.write(json.dumps(var)) # python 2.6
151 # this should never happen
152 print("unknown output format", format)
157 def save_rspec_to_file(rspec, filename):
158 if not filename.endswith(".rspec"):
159 filename = filename + ".rspec"
160 with open(filename, 'w') as f:
161 f.write("{}".format(rspec))
162 print("(Over)wrote {}".format(filename))
165 def save_record_to_file(filename, record_dict):
166 record = Record(dict=record_dict)
167 xml = record.save_as_xml()
168 with codecs.open(filename, encoding='utf-8', mode="w") as f:
170 print("(Over)wrote {}".format(filename))
173 def save_records_to_file(filename, record_dicts, format="xml"):
175 for index, record_dict in enumerate(record_dicts):
176 save_record_to_file(filename + "." + str(index), record_dict)
177 elif format == "xmllist":
178 with open(filename, "w") as f:
179 f.write("<recordlist>\n")
180 for record_dict in record_dicts:
181 record_obj = Record(dict=record_dict)
182 f.write('<record hrn="' + record_obj.hrn +
183 '" type="' + record_obj.type + '" />\n')
184 f.write("</recordlist>\n")
185 print("(Over)wrote {}".format(filename))
187 elif format == "hrnlist":
188 with open(filename, "w") as f:
189 for record_dict in record_dicts:
190 record_obj = Record(dict=record_dict)
191 f.write(record_obj.hrn + "\n")
192 print("(Over)wrote {}".format(filename))
195 # this should never happen
196 print("unknown output format", format)
198 # minimally check a key argument
201 def check_ssh_key(key):
202 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
203 return re.match(good_ssh_key, key, re.IGNORECASE)
208 def normalize_type(type):
209 if type.startswith('au'):
211 elif type.startswith('us'):
213 elif type.startswith('sl'):
215 elif type.startswith('no'):
217 elif type.startswith('ag'):
219 elif type.startswith('al'):
222 print('unknown type {} - should start with one of au|us|sl|no|ag|al'.format(type))
226 def load_record_from_opts(options):
228 if hasattr(options, 'xrn') and options.xrn:
229 if hasattr(options, 'type') and options.type:
230 xrn = Xrn(options.xrn, options.type)
232 xrn = Xrn(options.xrn)
233 record_dict['urn'] = xrn.get_urn()
234 record_dict['hrn'] = xrn.get_hrn()
235 record_dict['type'] = xrn.get_type()
236 if hasattr(options, 'key') and options.key:
238 pubkey = open(options.key, 'r').read()
241 if not check_ssh_key(pubkey):
242 raise SfaInvalidArgument(
243 name='key', msg="Could not find file, or wrong key format")
244 record_dict['reg-keys'] = [pubkey]
245 if hasattr(options, 'slices') and options.slices:
246 record_dict['slices'] = options.slices
247 if hasattr(options, 'reg_researchers') and options.reg_researchers is not None:
248 record_dict['reg-researchers'] = options.reg_researchers
249 if hasattr(options, 'email') and options.email:
250 record_dict['email'] = options.email
251 # authorities can have a name for standalone deployment
252 if hasattr(options, 'name') and options.name:
253 record_dict['name'] = options.name
254 if hasattr(options, 'reg_pis') and options.reg_pis:
255 record_dict['reg-pis'] = options.reg_pis
257 # handle extra settings
258 record_dict.update(options.extras)
260 return Record(dict=record_dict)
263 def load_record_from_file(filename):
264 with codecs.open(filename, encoding="utf-8", mode="r") as f:
266 return Record(xml=xml_str)
271 def unique_call_id(): return uuid.uuid4().urn
273 # a simple model for maintaing 3 doc attributes per command (instead of just one)
274 # essentially for the methods that implement a subcommand like sfi list
275 # we need to keep track of
276 # (*) doc a few lines that tell what it does, still located in __doc__
277 # (*) args_string a simple one-liner that describes mandatory arguments
278 # (*) example well, one or several releant examples
280 # since __doc__ only accounts for one, we use this simple mechanism below
281 # however we keep doc in place for easier migration
283 from functools import wraps
285 # we use a list as well as a dict so we can keep track of the order
290 def declare_command(args_string, example, aliases=None):
292 name = getattr(m, '__name__')
293 doc = getattr(m, '__doc__', "-- missing doc --")
294 doc = doc.strip(" \t\n")
295 commands_list.append(name)
296 # last item is 'canonical' name, so we can know which commands are
298 command_tuple = (doc, args_string, example, name)
299 commands_dict[name] = command_tuple
300 if aliases is not None:
301 for alias in aliases:
302 commands_list.append(alias)
303 commands_dict[alias] = command_tuple
306 def new_method(*args, **kwds): return m(*args, **kwds)
311 def remove_none_fields(record):
312 none_fields = [k for (k, v) in record.items() if v is None]
313 for k in none_fields:
321 # dirty hack to make this class usable from the outside
322 required_options = ['verbose', 'debug', 'registry',
323 'sm', 'auth', 'user', 'user_private_key']
326 def default_sfi_dir():
327 if os.path.isfile("./sfi_config"):
330 return os.path.expanduser("~/.sfi/")
332 # dummy to meet Sfi's expectations for its 'options' field
333 # i.e. s/t we can do setattr on
337 def __init__(self, options=None):
339 options = Sfi.DummyOptions()
340 for opt in Sfi.required_options:
341 if not hasattr(options, opt):
342 setattr(options, opt, None)
343 if not hasattr(options, 'sfi_dir'):
344 options.sfi_dir = Sfi.default_sfi_dir()
345 self.options = options
347 self.authority = None
348 self.logger = sfi_logger
349 self.logger.enable_console()
350 # various auxiliary material that we keep at hand
352 # need to call this other than just 'config' as we have a
353 # command/method with that name
354 self.config_instance = None
355 self.config_file = None
356 self.client_bootstrap = None
358 # suitable if no reasonable command has been provided
359 def print_commands_help(self, options):
360 verbose = getattr(options, 'verbose')
361 format3 = "%10s %-35s %s"
365 print(format3 % ("command", "cmd_args", "description"))
369 self.create_parser_global().print_help()
370 # preserve order from the code
371 for command in commands_list:
373 (doc, args_string, example, canonical) = commands_dict[command]
375 print("Cannot find info on command %s - skipped" % command)
379 if command == canonical:
380 doc = doc.replace("\n", "\n" + format3offset * ' ')
381 print(format3 % (command, args_string, doc))
383 self.create_parser_command(command).print_help()
385 print(format3 % (command, "<<alias for %s>>" % canonical, ""))
387 # now if a known command was found we can be more verbose on that one
388 def print_help(self):
389 print("==================== Generic sfi usage")
390 self.sfi_parser.print_help()
391 (doc, _, example, canonical) = commands_dict[self.command]
392 if canonical != self.command:
393 print("\n==================== NOTE: {} is an alias for genuine {}"
394 .format(self.command, canonical))
395 self.command = canonical
396 print("\n==================== Purpose of {}".format(self.command))
398 print("\n==================== Specific usage for {}".format(self.command))
399 self.command_parser.print_help()
401 print("\n==================== {} example(s)".format(self.command))
404 def create_parser_global(self):
405 # Generate command line parser
406 parser = OptionParser(add_help_option=False,
407 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
408 description="Commands: {}".format(" ".join(commands_list)))
409 parser.add_option("-r", "--registry", dest="registry",
410 help="root registry", metavar="URL", default=None)
411 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
412 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
413 parser.add_option("-R", "--raw", dest="raw", default=None,
414 help="Save raw, unparsed server response to a file")
415 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
416 help="raw file format ([text]|pickled|json)", default="text",
417 choices=("text", "pickled", "json"))
418 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
419 help="text string to write before and after raw output")
420 parser.add_option("-d", "--dir", dest="sfi_dir",
421 help="config & working directory - default is %default",
422 metavar="PATH", default=Sfi.default_sfi_dir())
423 parser.add_option("-u", "--user", dest="user",
424 help="user name", metavar="HRN", default=None)
425 parser.add_option("-a", "--auth", dest="auth",
426 help="authority name", metavar="HRN", default=None)
427 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
428 help="verbose mode - cumulative")
429 parser.add_option("-D", "--debug",
430 action="store_true", dest="debug", default=False,
431 help="Debug (xml-rpc) protocol messages")
432 # would it make sense to use ~/.ssh/id_rsa as a default here ?
433 parser.add_option("-k", "--private-key",
434 action="store", dest="user_private_key", default=None,
435 help="point to the private key file to use if not yet installed in sfi_dir")
436 parser.add_option("-t", "--timeout", dest="timeout", default=None,
437 help="Amout of time to wait before timing out the request")
438 parser.add_option("-h", "--help",
439 action="store_true", dest="help", default=False,
440 help="one page summary on commands & exit")
441 parser.disable_interspersed_args()
445 def create_parser_command(self, command):
446 if command not in commands_dict:
447 msg = "Invalid command\n"
449 msg += ','.join(commands_list)
450 self.logger.critical(msg)
453 # retrieve args_string
454 (_, args_string, __, canonical) = commands_dict[command]
456 parser = OptionParser(add_help_option=False,
457 usage="sfi [sfi_options] {} [cmd_options] {}"
458 .format(command, args_string))
459 parser.add_option("-h", "--help", dest='help', action='store_true', default=False,
460 help="Summary of one command usage")
462 if canonical in ("config"):
463 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
464 help='how myslice config variables as well')
466 if canonical in ("version"):
467 parser.add_option("-l", "--local",
468 action="store_true", dest="version_local", default=False,
469 help="display version of the local client")
471 if canonical in ("version", "trusted", "introspect"):
472 parser.add_option("-R", "--registry_interface",
473 action="store_true", dest="registry_interface", default=False,
474 help="target the registry interface instead of slice interface")
476 if canonical in ("register", "update"):
477 parser.add_option('-x', '--xrn', dest='xrn',
478 metavar='<xrn>', help='object hrn/urn (mandatory)')
479 parser.add_option('-t', '--type', dest='type', metavar='<type>',
480 help='object type (2 first chars is enough)', default=None)
481 parser.add_option('-e', '--email', dest='email',
482 default="", help="email (mandatory for users)")
483 parser.add_option('-n', '--name', dest='name',
484 default="", help="name (optional for authorities)")
485 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
487 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
488 default='', type="str", action='callback', callback=optparse_listvalue_callback)
489 parser.add_option('-r', '--researchers', dest='reg_researchers', metavar='<researchers>',
490 help='Set/replace slice researchers - use -r none to reset', default=None, type="str", action='callback',
491 callback=optparse_listvalue_callback)
492 parser.add_option('-p', '--pis', dest='reg_pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
493 default='', type="str", action='callback', callback=optparse_listvalue_callback)
494 parser.add_option('-X', '--extra', dest='extras', default={}, type='str', metavar="<EXTRA_ASSIGNS>",
495 action="callback", callback=optparse_dictvalue_callback, nargs=1,
496 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
498 # user specifies remote aggregate/sm/component
499 if canonical in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
500 "action", "shutdown", "renew", "status"):
501 parser.add_option("-d", "--delegate", dest="delegate", default=None,
503 help="Include a credential delegated to the user's root" +
504 "authority in set of credentials for this call")
506 # show_credential option
507 if canonical in ("list", "resources", "describe", "provision", "allocate", "register",
508 "update", "remove", "delete", "status", "renew"):
509 parser.add_option("-C", "--credential", dest='show_credential', action='store_true', default=False,
510 help="show credential(s) used in human-readable form")
511 if canonical in ("renew"):
512 parser.add_option("-l", "--as-long-as-possible", dest='alap', action='store_true', default=False,
513 help="renew as long as possible")
514 # registy filter option
515 if canonical in ("list", "show", "remove"):
516 parser.add_option("-t", "--type", dest="type", metavar="<type>",
518 help="type filter - 2 first chars is enough ([all]|user|slice|authority|node|aggregate)")
519 if canonical in ("show"):
520 parser.add_option("-k", "--key", dest="keys", action="append", default=[],
521 help="specify specific keys to be displayed from record")
522 parser.add_option("-n", "--no-details", dest="no_details", action="store_true", default=False,
523 help="call Resolve without the 'details' option")
524 if canonical in ("resources", "describe"):
526 parser.add_option("-r", "--rspec-version", dest="rspec_version", default=DEFAULT_RSPEC_VERSION,
527 help="schema type and version of resulting RSpec (default:{})".format(DEFAULT_RSPEC_VERSION))
528 # disable/enable cached rspecs
529 parser.add_option("-c", "--current", dest="current", default=False,
531 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
533 parser.add_option("-f", "--format", dest="format", type="choice",
534 help="display format ([xml]|dns|ip)", default="xml",
535 choices=("xml", "dns", "ip"))
536 # panos: a new option to define the type of information about
537 # resources a user is interested in
538 parser.add_option("-i", "--info", dest="info",
539 help="optional component information", default=None)
540 # a new option to retrieve or not reservation-oriented RSpecs
542 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
543 help="Retrieve or not reservation-oriented RSpecs ([resources]|leases|all)",
544 choices=("all", "resources", "leases"), default="resources")
546 if canonical in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
547 parser.add_option("-o", "--output", dest="file",
548 help="output XML to file", metavar="FILE", default=None)
550 if canonical in ("show", "list"):
551 parser.add_option("-f", "--format", dest="format", type="choice",
552 help="display format ([text]|xml)", default="text",
553 choices=("text", "xml"))
555 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
556 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
557 choices=("xml", "xmllist", "hrnlist"))
558 if canonical == 'list':
559 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
560 help="list all child records", default=False)
561 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
562 help="gives details, like user keys", default=False)
563 if canonical in ("delegate"):
564 parser.add_option("-u", "--user",
565 action="store_true", dest="delegate_user", default=False,
566 help="delegate your own credentials; default if no other option is provided")
567 parser.add_option("-s", "--slice", dest="delegate_slices", action='append', default=[],
568 metavar="slice_hrn", help="delegate cred. for slice HRN")
569 parser.add_option("-a", "--auths", dest='delegate_auths', action='append', default=[],
570 metavar='auth_hrn', help="delegate cred for auth HRN")
571 # this primarily is a shorthand for -A my_hrn^
572 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
573 help="delegate your PI credentials, so s.t. like -A your_hrn^")
574 parser.add_option("-A", "--to-authority", dest='delegate_to_authority', action='store_true', default=False,
575 help="""by default the mandatory argument is expected to be a user,
576 use this if you mean an authority instead""")
578 if canonical in ("myslice"):
579 parser.add_option("-p", "--password", dest='password', action='store', default=None,
580 help="specify mainfold password on the command line")
581 parser.add_option("-s", "--slice", dest="delegate_slices", action='append', default=[],
582 metavar="slice_hrn", help="delegate cred. for slice HRN")
583 parser.add_option("-a", "--auths", dest='delegate_auths', action='append', default=[],
584 metavar='auth_hrn', help="delegate PI cred for auth HRN")
585 parser.add_option('-d', '--delegate', dest='delegate',
586 help="Override 'delegate' from the config file")
587 parser.add_option('-b', '--backend', dest='backend',
588 help="Override 'backend' from the config file")
593 # Main: parse arguments and dispatch to command
595 def dispatch(self, command, command_options, command_args):
596 (doc, args_string, example, canonical) = commands_dict[command]
597 method = getattr(self, canonical, None)
599 print("sfi: unknown command {}".format(command))
600 raise SystemExit("Unknown command {}".format(command))
601 for arg in command_args:
602 if 'help' in arg or arg == '-h':
605 return method(command_options, command_args)
608 self.sfi_parser = self.create_parser_global()
609 (options, args) = self.sfi_parser.parse_args()
611 self.print_commands_help(options)
613 self.options = options
615 self.logger.setLevelFromOptVerbose(self.options.verbose)
618 self.logger.critical("No command given. Use -h for help.")
619 self.print_commands_help(options)
622 # complete / find unique match with command set
623 command_candidates = Candidates(commands_list)
625 command = command_candidates.only_match(input)
627 self.print_commands_help(options)
629 # second pass options parsing
630 self.command = command
631 self.command_parser = self.create_parser_command(command)
632 (command_options, command_args) = self.command_parser.parse_args(
634 if command_options.help:
637 self.command_options = command_options
639 # allow incoming types on 2 characters only
640 if hasattr(command_options, 'type'):
641 command_options.type = normalize_type(command_options.type)
642 if not command_options.type:
647 self.logger.debug("Command={}".format(self.command))
650 retcod = self.dispatch(command, command_options, command_args)
654 self.logger.log_exc("sfi command {} failed".format(command))
659 def read_config(self):
660 config_file = os.path.join(self.options.sfi_dir, "sfi_config")
661 shell_config_file = os.path.join(self.options.sfi_dir, "sfi_config.sh")
663 if Config.is_ini(config_file):
664 config = Config(config_file)
666 # try upgrading from shell config format
667 fp, fn = mkstemp(suffix='sfi_config', text=True)
669 # we need to preload the sections we want parsed
670 # from the shell config
671 config.add_section('sfi')
672 # sface users should be able to use this same file to configure
674 config.add_section('sface')
675 # manifold users should be able to specify the details
676 # of their backend server here for 'sfi myslice'
677 config.add_section('myslice')
678 config.load(config_file)
680 shutil.move(config_file, shell_config_file)
682 config.save(config_file)
685 self.logger.critical(
686 "Failed to read configuration file {}".format(config_file))
688 "Make sure to remove the export clauses and to add quotes")
689 if self.options.verbose == 0:
690 self.logger.info("Re-run with -v for more details")
693 "Could not read config file {}".format(config_file))
696 self.config_instance = config
699 if (self.options.sm is not None):
700 self.sm_url = self.options.sm
701 elif hasattr(config, "SFI_SM"):
702 self.sm_url = config.SFI_SM
705 "You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in {}".format(config_file))
709 if (self.options.registry is not None):
710 self.reg_url = self.options.registry
711 elif hasattr(config, "SFI_REGISTRY"):
712 self.reg_url = config.SFI_REGISTRY
715 "You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in {}".format(config_file))
719 if (self.options.user is not None):
720 self.user = self.options.user
721 elif hasattr(config, "SFI_USER"):
722 self.user = config.SFI_USER
725 "You need to set e.g. SFI_USER='plc.princeton.username' in {}".format(config_file))
729 if (self.options.auth is not None):
730 self.authority = self.options.auth
731 elif hasattr(config, "SFI_AUTH"):
732 self.authority = config.SFI_AUTH
735 "You need to set e.g. SFI_AUTH='plc.princeton' in {}".format(config_file))
738 self.config_file = config_file
743 # Get various credential and spec files
745 # Establishes limiting conventions
746 # - conflates MAs and SAs
747 # - assumes last token in slice name is unique
749 # Bootstraps credentials
750 # - bootstrap user credential from self-signed certificate
751 # - bootstrap authority credential from user credential
752 # - bootstrap slice credential from user credential
755 # init self-signed cert, user credentials and gid
757 if self.options.verbose:
759 "Initializing SfaClientBootstrap with {}".format(self.reg_url))
760 client_bootstrap = SfaClientBootstrap(self.user, self.reg_url, self.options.sfi_dir,
762 # if -k is provided, use this to initialize private key
763 if self.options.user_private_key:
764 client_bootstrap.init_private_key_if_missing(
765 self.options.user_private_key)
767 # trigger legacy compat code if needed
768 # the name has changed from just <leaf>.pkey to <hrn>.pkey
769 if not os.path.isfile(client_bootstrap.private_key_filename()):
770 self.logger.info("private key not found, trying legacy name")
772 legacy_private_key = os.path.join(self.options.sfi_dir, "{}.pkey"
773 .format(Xrn.unescape(get_leaf(self.user))))
774 self.logger.debug("legacy_private_key={}"
775 .format(legacy_private_key))
776 client_bootstrap.init_private_key_if_missing(
778 self.logger.info("Copied private key from legacy location {}"
779 .format(legacy_private_key))
781 self.logger.log_exc("Can't find private key ")
785 client_bootstrap.bootstrap_my_gid()
786 # extract what's needed
787 self.private_key = client_bootstrap.private_key()
788 self.my_credential_string = client_bootstrap.my_credential_string()
789 self.my_credential = {'geni_type': 'geni_sfa',
791 'geni_value': self.my_credential_string}
792 self.my_gid = client_bootstrap.my_gid()
793 self.client_bootstrap = client_bootstrap
795 def my_authority_credential_string(self):
796 if not self.authority:
797 self.logger.critical(
798 "no authority specified. Use -a or set SF_AUTH")
800 return self.client_bootstrap.authority_credential_string(self.authority)
802 def authority_credential_string(self, auth_hrn):
803 return self.client_bootstrap.authority_credential_string(auth_hrn)
805 def slice_credential_string(self, name):
806 return self.client_bootstrap.slice_credential_string(name)
808 def slice_credential(self, name):
809 return {'geni_type': 'geni_sfa',
811 'geni_value': self.slice_credential_string(name)}
813 # xxx should be supported by sfaclientbootstrap as well
814 def delegate_cred(self, object_cred, hrn, type='authority'):
815 # the gid and hrn of the object we are delegating
816 if isinstance(object_cred, str):
817 object_cred = Credential(string=object_cred)
818 object_gid = object_cred.get_gid_object()
819 object_hrn = object_gid.get_hrn()
821 if not object_cred.get_privileges().get_all_delegate():
822 self.logger.error("Object credential {} does not have delegate bit set"
826 # the delegating user's gid
827 caller_gidfile = self.my_gid()
829 # the gid of the user who will be delegated to
830 delegee_gid = self.client_bootstrap.gid(hrn, type)
831 delegee_hrn = delegee_gid.get_hrn()
832 dcred = object_cred.delegate(
833 delegee_gid, self.private_key, caller_gidfile)
834 return dcred.save_to_string(save_parents=True)
837 # Management of the servers
842 if not hasattr(self, 'registry_proxy'):
843 self.logger.info("Contacting Registry at: {}".format(self.reg_url))
844 self.registry_proxy \
845 = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
846 timeout=self.options.timeout, verbose=self.options.debug)
847 return self.registry_proxy
851 if not hasattr(self, 'sliceapi_proxy'):
852 # if the command exposes the --component option, figure it's
853 # hostname and connect at CM_PORT
854 if hasattr(self.command_options, 'component') and self.command_options.component:
855 # resolve the hrn at the registry
856 node_hrn = self.command_options.component
857 records = self.registry().Resolve(node_hrn, self.my_credential_string)
858 records = filter_records('node', records)
861 "No such component:{}".format(opts.component))
863 cm_url = "http://{}:{}/".format(record['hostname'], CM_PORT)
864 self.sliceapi_proxy = SfaServerProxy(
865 cm_url, self.private_key, self.my_gid)
867 # otherwise use what was provided as --sliceapi, or SFI_SM in
869 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
870 self.sm_url = 'http://' + self.sm_url
872 "Contacting Slice Manager at: {}".format(self.sm_url))
873 self.sliceapi_proxy \
874 = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
875 timeout=self.options.timeout, verbose=self.options.debug)
876 return self.sliceapi_proxy
878 def get_cached_server_version(self, server):
879 # check local cache first
882 cache_file = os.path.join(self.options.sfi_dir, 'sfi_cache.dat')
883 cache_key = server.url + "-version"
885 cache = Cache(cache_file)
888 self.logger.info("Local cache not found at: {}".format(cache_file))
891 version = cache.get(cache_key)
894 result = server.GetVersion()
895 version = ReturnValue.get_value(result)
896 # cache version for 20 minutes
897 cache.add(cache_key, version, ttl=60 * 20)
898 self.logger.info("Updating cache file {}".format(cache_file))
899 cache.save_to_file(cache_file)
903 # resurrect this temporarily so we can support V1 aggregates for a while
904 def server_supports_options_arg(self, server):
906 Returns true if server support the optional call_id arg, false otherwise.
908 server_version = self.get_cached_server_version(server)
910 # xxx need to rewrite this
911 if int(server_version.get('geni_api')) >= 2:
915 def server_supports_call_id_arg(self, server):
916 server_version = self.get_cached_server_version(server)
918 if 'sfa' in server_version and 'code_tag' in server_version:
919 code_tag = server_version['code_tag']
920 code_tag_parts = code_tag.split("-")
921 version_parts = code_tag_parts[0].split(".")
922 major, minor = version_parts[0], version_parts[1]
923 rev = code_tag_parts[1]
924 if int(major) == 1 and minor == 0 and build >= 22:
928 # ois = options if supported
929 # to be used in something like serverproxy.Method(arg1, arg2,
930 # *self.ois(api_options))
931 def ois(self, server, option_dict):
932 if self.server_supports_options_arg(server):
934 elif self.server_supports_call_id_arg(server):
935 return [unique_call_id()]
939 # cis = call_id if supported - like ois
940 def cis(self, server):
941 if self.server_supports_call_id_arg(server):
942 return [unique_call_id]
947 def get_rspec_file(self, rspec):
948 if (os.path.isabs(rspec)):
951 file = os.path.join(self.options.sfi_dir, rspec)
952 if (os.path.isfile(file)):
955 self.logger.critical("No such rspec file {}".format(rspec))
958 def get_record_file(self, record):
959 if (os.path.isabs(record)):
962 file = os.path.join(self.options.sfi_dir, record)
963 if (os.path.isfile(file)):
966 self.logger.critical(
967 "No such registry record file {}".format(record))
970 # helper function to analyze raw output
971 # for main : return 0 if everything is fine, something else otherwise
973 def success(self, raw):
974 return_value = ReturnValue(raw)
975 output = ReturnValue.get_output(return_value)
976 # means everything is fine
979 # something went wrong
980 print('ERROR:', output)
983 #==========================================================================
984 # Following functions implement the commands
986 # Registry-related commands
987 #==========================================================================
989 @declare_command("", "")
990 def config(self, options, args):
992 Display contents of current config
998 print("# From configuration file {}".format(self.config_file))
999 flags = [('sfi', [('registry', 'reg_url'),
1000 ('auth', 'authority'),
1007 ('myslice', ['backend', 'delegate', 'platform', 'username']))
1009 for (section, tuples) in flags:
1010 print("[{}]".format(section))
1012 for external_name, internal_name in tuples:
1013 print("{:<20} = {}".format(
1014 external_name, getattr(self, internal_name)))
1016 for external_name, internal_name in tuples:
1017 varname = "{}_{}".format(
1018 section.upper(), external_name.upper())
1019 value = getattr(self.config_instance, varname)
1020 print("{:<20} = {}".format(external_name, value))
1021 # xxx should analyze result
1024 @declare_command("", "")
1025 def version(self, options, args):
1027 display an SFA server version (GetVersion)
1028 or version information about sfi itself
1034 if options.version_local:
1035 version = version_core()
1037 if options.registry_interface:
1038 server = self.registry()
1040 server = self.sliceapi()
1041 result = server.GetVersion()
1042 version = ReturnValue.get_value(result)
1043 if self.options.raw:
1044 save_raw_to_file(result, self.options.raw,
1045 self.options.rawformat, self.options.rawbanner)
1047 pprinter = PrettyPrinter(indent=4)
1048 pprinter.pprint(version)
1049 # xxx should analyze result
1052 @declare_command("authority", "")
1053 def list(self, options, args):
1055 list entries in named authority registry (List)
1063 if options.recursive:
1064 opts['recursive'] = options.recursive
1066 if options.show_credential:
1067 show_credentials(self.my_credential_string)
1069 list = self.registry().List(hrn, self.my_credential_string, options)
1071 raise Exception("Not enough parameters for the 'list' command")
1073 # filter on person, slice, site, node, etc.
1074 # This really should be in the self.filter_records funct def comment...
1075 list = filter_records(options.type, list)
1076 terminal_render(list, options)
1078 save_records_to_file(options.file, list, options.fileformat)
1079 # xxx should analyze result
1082 @declare_command("name", "")
1083 def show(self, options, args):
1085 show details about named registry record (Resolve)
1092 # explicitly require Resolve to run in details mode
1093 resolve_options = {}
1094 if not options.no_details:
1095 resolve_options['details'] = True
1096 record_dicts = self.registry().Resolve(
1097 hrn, self.my_credential_string, resolve_options)
1098 record_dicts = filter_records(options.type, record_dicts)
1099 if not record_dicts:
1100 self.logger.error("No record of type {}".format(options.type))
1102 # user has required to focus on some keys
1104 def project(record):
1106 for key in options.keys:
1108 projected[key] = record[key]
1112 record_dicts = [project(record) for record in record_dicts]
1113 records = [Record(dict=record_dict) for record_dict in record_dicts]
1114 for record in records:
1115 if (options.format == "text"):
1116 record.dump(sort=True)
1118 print(record.save_as_xml())
1120 save_records_to_file(
1121 options.file, record_dicts, options.fileformat)
1122 # xxx should analyze result
1125 # this historically was named 'add', it is now 'register' with an alias
1127 @declare_command("[xml-filename]", "", ['add'])
1128 def register(self, options, args):
1130 create new record in registry (Register)
1131 from command line options (recommended)
1132 old-school method involving an xml file still supported
1138 auth_cred = self.my_authority_credential_string()
1139 if options.show_credential:
1140 show_credentials(auth_cred)
1144 record_filepath = args[0]
1145 rec_file = self.get_record_file(record_filepath)
1146 record_dict.update(load_record_from_file(
1147 rec_file).record_to_dict())
1149 print("Cannot load record file {}".format(record_filepath))
1152 record_dict.update(load_record_from_opts(options).record_to_dict())
1153 # we should have a type by now
1154 if 'type' not in record_dict:
1157 # this is still planetlab dependent.. as plc will whine without that
1158 # also, it's only for adding
1159 if record_dict['type'] == 'user':
1160 if not 'first_name' in record_dict:
1161 record_dict['first_name'] = record_dict['hrn']
1162 if 'last_name' not in record_dict:
1163 record_dict['last_name'] = record_dict['hrn']
1164 register = self.registry().Register(record_dict, auth_cred)
1165 # xxx looks like the result here is not ReturnValue-compatible
1166 # return self.success (register)
1167 # xxx should analyze result
1170 @declare_command("[xml-filename]", "")
1171 def update(self, options, args):
1173 update record into registry (Update)
1174 from command line options (recommended)
1175 old-school method involving an xml file still supported
1183 record_filepath = args[0]
1184 rec_file = self.get_record_file(record_filepath)
1185 record_dict.update(load_record_from_file(
1186 rec_file).record_to_dict())
1188 record_dict.update(load_record_from_opts(options).record_to_dict())
1189 # at the very least we need 'type' here
1190 if 'type' not in record_dict or record_dict['type'] is None:
1194 # don't translate into an object, as this would possibly distort
1195 # user-provided data; e.g. add an 'email' field to Users
1196 if record_dict['type'] in ['user']:
1197 if record_dict['hrn'] == self.user:
1198 cred = self.my_credential_string
1200 cred = self.my_authority_credential_string()
1201 elif record_dict['type'] in ['slice']:
1203 cred = self.slice_credential_string(record_dict['hrn'])
1204 except ServerException as e:
1205 # XXX smbaker -- once we have better error return codes, update this
1206 # to do something better than a string compare
1207 if "Permission error" in e.args[0]:
1208 cred = self.my_authority_credential_string()
1211 elif record_dict['type'] in ['authority']:
1212 cred = self.my_authority_credential_string()
1213 elif record_dict['type'] in ['node']:
1214 cred = self.my_authority_credential_string()
1217 "unknown record type {}".format(record_dict['type']))
1218 if options.show_credential:
1219 show_credentials(cred)
1220 update = self.registry().Update(record_dict, cred)
1221 # xxx looks like the result here is not ReturnValue-compatible
1222 # return self.success(update)
1223 # xxx should analyze result
1226 @declare_command("hrn", "")
1227 def remove(self, options, args):
1229 remove registry record by name (Remove)
1231 auth_cred = self.my_authority_credential_string()
1240 if options.show_credential:
1241 show_credentials(auth_cred)
1242 remove = self.registry().Remove(hrn, auth_cred, type)
1243 # xxx looks like the result here is not ReturnValue-compatible
1244 # return self.success (remove)
1245 # xxx should analyze result
1248 # ==================================================================
1249 # Slice-related commands
1250 # ==================================================================
1252 # show rspec for named slice
1253 @declare_command("", "", ['discover'])
1254 def resources(self, options, args):
1256 discover available resources (ListResources)
1262 server = self.sliceapi()
1264 creds = [self.my_credential_string]
1265 if options.delegate:
1266 creds.append(self.delegate_cred(
1267 cred, get_authority(self.authority)))
1268 if options.show_credential:
1269 show_credentials(creds)
1271 # no need to check if server accepts the options argument since the options has
1272 # been a required argument since v1 API
1274 # always send call_id to v2 servers
1275 api_options['call_id'] = unique_call_id()
1276 # ask for cached value if available
1277 api_options['cached'] = True
1279 api_options['info'] = options.info
1280 if options.list_leases:
1281 api_options['list_leases'] = options.list_leases
1283 if options.current == True:
1284 api_options['cached'] = False
1286 api_options['cached'] = True
1287 version_manager = VersionManager()
1288 api_options['geni_rspec_version'] = version_manager.get_version(
1289 options.rspec_version).to_dict()
1291 list_resources = server.ListResources(creds, api_options)
1292 value = ReturnValue.get_value(list_resources)
1293 if self.options.raw:
1294 save_raw_to_file(list_resources, self.options.raw,
1295 self.options.rawformat, self.options.rawbanner)
1296 if options.file is not None:
1297 save_rspec_to_file(value, options.file)
1298 if (self.options.raw is None) and (options.file is None):
1299 display_rspec(value, options.format)
1300 return self.success(list_resources)
1302 @declare_command("slice_hrn", "")
1303 def describe(self, options, args):
1305 shows currently allocated/provisioned resources
1306 of the named slice or set of slivers (Describe)
1312 server = self.sliceapi()
1314 creds = [self.slice_credential(args[0])]
1315 if options.delegate:
1316 creds.append(self.delegate_cred(
1317 cred, get_authority(self.authority)))
1318 if options.show_credential:
1319 show_credentials(creds)
1321 api_options = {'call_id': unique_call_id(),
1323 'info': options.info,
1324 'list_leases': options.list_leases,
1325 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1328 api_options['info'] = options.info
1330 if options.rspec_version:
1331 version_manager = VersionManager()
1332 server_version = self.get_cached_server_version(server)
1333 if 'sfa' in server_version:
1334 # just request the version the client wants
1335 api_options['geni_rspec_version'] = version_manager.get_version(
1336 options.rspec_version).to_dict()
1338 api_options['geni_rspec_version'] = {
1339 'type': 'geni', 'version': '3'}
1340 urn = Xrn(args[0], type='slice').get_urn()
1341 remove_none_fields(api_options)
1342 describe = server.Describe([urn], creds, api_options)
1343 value = ReturnValue.get_value(describe)
1344 if self.options.raw:
1345 save_raw_to_file(describe, self.options.raw,
1346 self.options.rawformat, self.options.rawbanner)
1347 if options.file is not None:
1348 save_rspec_to_file(value['geni_rspec'], options.file)
1349 if (self.options.raw is None) and (options.file is None):
1350 display_rspec(value['geni_rspec'], options.format)
1351 return self.success(describe)
1353 @declare_command("slice_hrn [<sliver_urn>...]", "")
1354 def delete(self, options, args):
1356 de-allocate and de-provision all or named slivers of the named slice (Delete)
1362 server = self.sliceapi()
1365 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1368 # we have sliver urns
1369 sliver_urns = args[1:]
1371 # we provision all the slivers of the slice
1372 sliver_urns = [slice_urn]
1375 slice_cred = self.slice_credential(slice_hrn)
1376 creds = [slice_cred]
1378 # options and call_id when supported
1380 api_options['call_id'] = unique_call_id()
1381 if options.show_credential:
1382 show_credentials(creds)
1383 delete = server.Delete(sliver_urns, creds, *
1384 self.ois(server, api_options))
1385 value = ReturnValue.get_value(delete)
1386 if self.options.raw:
1387 save_raw_to_file(delete, self.options.raw,
1388 self.options.rawformat, self.options.rawbanner)
1391 return self.success(delete)
1393 @declare_command("slice_hrn rspec", "")
1394 def allocate(self, options, args):
1396 allocate resources to the named slice (Allocate)
1402 server = self.sliceapi()
1403 server_version = self.get_cached_server_version(server)
1405 rspec_file = self.get_rspec_file(args[1])
1407 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1410 creds = [self.slice_credential(slice_hrn)]
1412 delegated_cred = None
1413 if server_version.get('interface') == 'slicemgr':
1414 # delegate our cred to the slice manager
1415 # do not delegate cred to slicemgr...not working at the moment
1417 # if server_version.get('hrn'):
1418 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1419 # elif server_version.get('urn'):
1420 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1422 if options.show_credential:
1423 show_credentials(creds)
1427 api_options['call_id'] = unique_call_id()
1431 slice_records = self.registry().Resolve(
1432 slice_urn, [self.my_credential_string])
1433 remove_none_fields(slice_records[0])
1434 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers'] != []:
1435 slice_record = slice_records[0]
1436 user_hrns = slice_record['reg-researchers']
1437 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1438 user_records = self.registry().Resolve(
1439 user_urns, [self.my_credential_string])
1440 sfa_users = sfa_users_arg(user_records, slice_record)
1441 geni_users = pg_users_arg(user_records)
1443 api_options['sfa_users'] = sfa_users
1444 api_options['geni_users'] = geni_users
1446 with open(rspec_file) as rspec:
1447 rspec_xml = rspec.read()
1448 allocate = server.Allocate(
1449 slice_urn, creds, rspec_xml, api_options)
1450 value = ReturnValue.get_value(allocate)
1451 if self.options.raw:
1452 save_raw_to_file(allocate, self.options.raw,
1453 self.options.rawformat, self.options.rawbanner)
1454 if options.file is not None:
1455 save_rspec_to_file(value['geni_rspec'], options.file)
1456 if (self.options.raw is None) and (options.file is None):
1458 return self.success(allocate)
1460 @declare_command("slice_hrn [<sliver_urn>...]", "")
1461 def provision(self, options, args):
1463 provision all or named already allocated slivers of the named slice (Provision)
1469 server = self.sliceapi()
1470 server_version = self.get_cached_server_version(server)
1472 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1474 # we have sliver urns
1475 sliver_urns = args[1:]
1477 # we provision all the slivers of the slice
1478 sliver_urns = [slice_urn]
1481 creds = [self.slice_credential(slice_hrn)]
1482 delegated_cred = None
1483 if server_version.get('interface') == 'slicemgr':
1484 # delegate our cred to the slice manager
1485 # do not delegate cred to slicemgr...not working at the moment
1487 # if server_version.get('hrn'):
1488 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1489 # elif server_version.get('urn'):
1490 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1492 if options.show_credential:
1493 show_credentials(creds)
1496 api_options['call_id'] = unique_call_id()
1498 # set the requtested rspec version
1499 version_manager = VersionManager()
1500 rspec_version = version_manager._get_version('geni', '3').to_dict()
1501 api_options['geni_rspec_version'] = rspec_version
1504 # need to pass along user keys to the aggregate.
1506 # { urn: urn:publicid:IDN+emulab.net+user+alice
1507 # keys: [<ssh key A>, <ssh key B>]
1510 slice_records = self.registry().Resolve(
1511 slice_urn, [self.my_credential_string])
1512 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers'] != []:
1513 slice_record = slice_records[0]
1514 user_hrns = slice_record['reg-researchers']
1515 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1516 user_records = self.registry().Resolve(
1517 user_urns, [self.my_credential_string])
1518 users = pg_users_arg(user_records)
1520 api_options['geni_users'] = users
1521 provision = server.Provision(sliver_urns, creds, api_options)
1522 value = ReturnValue.get_value(provision)
1523 if self.options.raw:
1524 save_raw_to_file(provision, self.options.raw,
1525 self.options.rawformat, self.options.rawbanner)
1526 if options.file is not None:
1527 save_rspec_to_file(value['geni_rspec'], options.file)
1528 if (self.options.raw is None) and (options.file is None):
1530 return self.success(provision)
1532 @declare_command("slice_hrn", "")
1533 def status(self, options, args):
1535 retrieve the status of the slivers belonging to the named slice (Status)
1541 server = self.sliceapi()
1544 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1547 slice_cred = self.slice_credential(slice_hrn)
1548 creds = [slice_cred]
1550 # options and call_id when supported
1552 api_options['call_id'] = unique_call_id()
1553 if options.show_credential:
1554 show_credentials(creds)
1555 status = server.Status([slice_urn], creds, *
1556 self.ois(server, api_options))
1557 value = ReturnValue.get_value(status)
1558 if self.options.raw:
1559 save_raw_to_file(status, self.options.raw,
1560 self.options.rawformat, self.options.rawbanner)
1563 return self.success(status)
1565 @declare_command("slice_hrn [<sliver_urn>...] action", "")
1566 def action(self, options, args):
1568 Perform the named operational action on all or named slivers of the named slice
1574 server = self.sliceapi()
1578 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1580 # we have sliver urns
1581 sliver_urns = args[1:-1]
1583 # we provision all the slivers of the slice
1584 sliver_urns = [slice_urn]
1587 slice_cred = self.slice_credential(args[0])
1588 creds = [slice_cred]
1589 if options.delegate:
1590 delegated_cred = self.delegate_cred(
1591 slice_cred, get_authority(self.authority))
1592 creds.append(delegated_cred)
1594 perform_action = server.PerformOperationalAction(
1595 sliver_urns, creds, action, api_options)
1596 value = ReturnValue.get_value(perform_action)
1597 if self.options.raw:
1598 save_raw_to_file(perform_action, self.options.raw,
1599 self.options.rawformat, self.options.rawbanner)
1602 return self.success(perform_action)
1604 @declare_command("slice_hrn [<sliver_urn>...] time",
1605 "\n".join(["sfi renew onelab.ple.heartbeat 2015-04-31",
1606 "sfi renew onelab.ple.heartbeat 2015-04-31T14:00:00Z",
1607 "sfi renew onelab.ple.heartbeat +5d",
1608 "sfi renew onelab.ple.heartbeat +3w",
1609 "sfi renew onelab.ple.heartbeat +2m", ]))
1610 def renew(self, options, args):
1618 server = self.sliceapi()
1620 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1623 # we have sliver urns
1624 sliver_urns = args[1:-1]
1626 # we provision all the slivers of the slice
1627 sliver_urns = [slice_urn]
1628 input_time = args[-1]
1630 # time: don't try to be smart on the time format, server-side will
1632 slice_cred = self.slice_credential(args[0])
1633 creds = [slice_cred]
1634 # options and call_id when supported
1636 api_options['call_id'] = unique_call_id()
1638 api_options['geni_extend_alap'] = True
1639 if options.show_credential:
1640 show_credentials(creds)
1641 renew = server.Renew(sliver_urns, creds, input_time,
1642 *self.ois(server, api_options))
1643 value = ReturnValue.get_value(renew)
1644 if self.options.raw:
1645 save_raw_to_file(renew, self.options.raw,
1646 self.options.rawformat, self.options.rawbanner)
1649 return self.success(renew)
1651 @declare_command("slice_hrn", "")
1652 def shutdown(self, options, args):
1654 shutdown named slice (Shutdown)
1660 server = self.sliceapi()
1663 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1665 slice_cred = self.slice_credential(slice_hrn)
1666 creds = [slice_cred]
1667 shutdown = server.Shutdown(slice_urn, creds)
1668 value = ReturnValue.get_value(shutdown)
1669 if self.options.raw:
1670 save_raw_to_file(shutdown, self.options.raw,
1671 self.options.rawformat, self.options.rawbanner)
1674 return self.success(shutdown)
1676 @declare_command("[name]", "")
1677 def gid(self, options, args):
1679 Create a GID (CreateGid)
1685 target_hrn = args[0]
1686 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1687 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1689 filename = options.file
1691 filename = os.sep.join(
1692 [self.options.sfi_dir, '{}.gid'.format(target_hrn)])
1693 self.logger.info("writing {} gid to {}".format(target_hrn, filename))
1694 GID(string=gid).save_to_file(filename)
1695 # xxx should analyze result
1698 ####################
1699 @declare_command("to_hrn", """$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1701 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1702 the set of credentials in the scope for this call would be
1703 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1705 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1707 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1708 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1709 because of the two -s options
1712 def delegate(self, options, args):
1714 (locally) create delegate credential for use by given hrn
1715 make sure to check for 'sfi myslice' instead if you plan
1723 # support for several delegations in the same call
1724 # so first we gather the things to do
1726 for slice_hrn in options.delegate_slices:
1727 message = "{}.slice".format(slice_hrn)
1728 original = self.slice_credential_string(slice_hrn)
1729 tuples.append((message, original,))
1730 if options.delegate_pi:
1731 my_authority = self.authority
1732 message = "{}.pi".format(my_authority)
1733 original = self.my_authority_credential_string()
1734 tuples.append((message, original,))
1735 for auth_hrn in options.delegate_auths:
1736 message = "{}.auth".format(auth_hrn)
1737 original = self.authority_credential_string(auth_hrn)
1738 tuples.append((message, original, ))
1739 # if nothing was specified at all at this point, let's assume -u
1741 options.delegate_user = True
1743 if options.delegate_user:
1744 message = "{}.user".format(self.user)
1745 original = self.my_credential_string
1746 tuples.append((message, original, ))
1748 # default type for beneficial is user unless -A
1749 to_type = 'authority' if options.delegate_to_authority else 'user'
1751 # let's now handle all this
1752 # it's all in the filenaming scheme
1753 for (message, original) in tuples:
1754 delegated_string = self.client_bootstrap.delegate_credential_string(
1755 original, to_hrn, to_type)
1756 delegated_credential = Credential(string=delegated_string)
1757 filename = os.path.join(self.options.sfi_dir,
1758 "{}_for_{}.{}.cred".format(message, to_hrn, to_type))
1759 delegated_credential.save_to_file(filename, save_parents=True)
1760 self.logger.info("delegated credential for {} to {} and wrote to {}"
1761 .format(message, to_hrn, filename))
1763 ####################
1764 @declare_command("", """$ less +/myslice sfi_config
1766 backend = http://manifold.pl.sophia.inria.fr:7080
1767 # the HRN that myslice uses, so that we are delegating to
1768 delegate = ple.upmc.slicebrowser
1769 # platform - this is a myslice concept
1771 # username - as of this writing (May 2013) a simple login name
1775 will first collect the slices that you are part of, then make sure
1776 all your credentials are up-to-date (read: refresh expired ones)
1777 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1778 and upload them all on myslice backend, using 'platform' and 'user'.
1779 A password will be prompted for the upload part.
1781 $ sfi -v myslice -- or sfi -vv myslice
1782 same but with more and more verbosity
1784 $ sfi m -b http://mymanifold.foo.com:7080/
1785 is synonym to sfi myslice as no other command starts with an 'm'
1786 and uses a custom backend for this one call
1789 def myslice(self, options, args):
1790 """ This helper is for refreshing your credentials at myslice; it will
1791 * compute all the slices that you currently have credentials on
1792 * refresh all your credentials (you as a user and pi, your slices)
1793 * upload them to the manifold backend server
1794 for last phase, sfi_config is read to look for the [myslice] section,
1795 and namely the 'backend', 'delegate' and 'user' settings
1802 # enable info by default
1803 self.logger.setLevelFromOptVerbose(self.options.verbose + 1)
1804 # the rough sketch goes like this
1805 # (0) produce a p12 file
1806 self.client_bootstrap.my_pkcs12()
1808 # (a) rain check for sufficient config in sfi_config
1810 myslice_keys = ['backend', 'delegate', 'platform', 'username']
1811 for key in myslice_keys:
1813 # oct 2013 - I'm finding myself juggling with config files
1814 # so a couple of command-line options can now override config
1815 if hasattr(options, key) and getattr(options, key) is not None:
1816 value = getattr(options, key)
1818 full_key = "MYSLICE_" + key.upper()
1819 value = getattr(self.config_instance, full_key, None)
1821 myslice_dict[key] = value
1823 print("Unsufficient config, missing key {} in [myslice] section of sfi_config"
1825 if len(myslice_dict) != len(myslice_keys):
1828 # (b) figure whether we are PI for the authority where we belong
1829 self.logger.info("Resolving our own id {}".format(self.user))
1830 my_records = self.registry().Resolve(self.user, self.my_credential_string)
1831 if len(my_records) != 1:
1832 print("Cannot Resolve {} -- exiting".format(self.user))
1834 my_record = my_records[0]
1835 my_auths_all = my_record['reg-pi-authorities']
1837 "Found {} authorities that we are PI for".format(len(my_auths_all)))
1838 self.logger.debug("They are {}".format(my_auths_all))
1840 my_auths = my_auths_all
1841 if options.delegate_auths:
1842 my_auths = list(set(my_auths_all).intersection(
1843 set(options.delegate_auths)))
1845 "Restricted to user-provided auths {}".format(my_auths))
1847 # (c) get the set of slices that we are in
1848 my_slices_all = my_record['reg-slices']
1850 "Found {} slices that we are member of".format(len(my_slices_all)))
1851 self.logger.debug("They are: {}".format(my_slices_all))
1853 my_slices = my_slices_all
1854 # if user provided slices, deal only with these - if they are found
1855 if options.delegate_slices:
1856 my_slices = list(set(my_slices_all).intersection(
1857 set(options.delegate_slices)))
1859 "Restricted to user-provided slices: {}".format(my_slices))
1861 # (d) make sure we have *valid* credentials for all these
1862 hrn_credentials = []
1863 hrn_credentials.append((self.user, 'user', self.my_credential_string,))
1864 for auth_hrn in my_auths:
1865 hrn_credentials.append(
1866 (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),))
1867 for slice_hrn in my_slices:
1869 hrn_credentials.append(
1870 (slice_hrn, 'slice', self.slice_credential_string(slice_hrn),))
1872 print("WARNING: could not get slice credential for slice {}"
1875 # (e) check for the delegated version of these
1876 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1877 # switch to myslice using an authority instead of a user
1878 delegatee_type = 'user'
1879 delegatee_hrn = myslice_dict['delegate']
1880 hrn_delegated_credentials = []
1881 for (hrn, htype, credential) in hrn_credentials:
1882 delegated_credential = self.client_bootstrap.delegate_credential_string(
1883 credential, delegatee_hrn, delegatee_type)
1884 # save these so user can monitor what she's uploaded
1885 filename = os.path.join(self.options.sfi_dir,
1886 "{}.{}_for_{}.{}.cred"
1887 .format(hrn, htype, delegatee_hrn, delegatee_type))
1888 with open(filename, 'w') as f:
1889 f.write(delegated_credential)
1890 self.logger.debug("(Over)wrote {}".format(filename))
1891 hrn_delegated_credentials.append(
1892 (hrn, htype, delegated_credential, filename, ))
1894 # (f) and finally upload them to manifold server
1895 # xxx todo add an option so the password can be set on the command line
1896 # (but *NOT* in the config file) so other apps can leverage this
1897 self.logger.info("Uploading on backend at {}".format(
1898 myslice_dict['backend']))
1899 uploader = ManifoldUploader(logger=self.logger,
1900 url=myslice_dict['backend'],
1901 platform=myslice_dict['platform'],
1902 username=myslice_dict['username'],
1903 password=options.password)
1904 uploader.prompt_all()
1905 (count_all, count_success) = (0, 0)
1906 for (hrn, htype, delegated_credential, filename) in hrn_delegated_credentials:
1908 inspect = Credential(string=delegated_credential)
1909 expire_datetime = inspect.get_expiration()
1910 message = "{} ({}) [exp:{}]".format(hrn, htype, expire_datetime)
1911 if uploader.upload(delegated_credential, message=message):
1914 self.logger.info("Successfully uploaded {}/{} credentials"
1915 .format(count_success, count_all))
1917 # at first I thought we would want to save these,
1918 # like 'sfi delegate does' but on second thought
1919 # it is probably not helpful as people would not
1920 # need to run 'sfi delegate' at all anymore
1921 if count_success != count_all:
1923 # xxx should analyze result
1926 @declare_command("cred", "")
1927 def trusted(self, options, args):
1929 return the trusted certs at this interface (get_trusted_certs)
1931 if options.registry_interface:
1932 server = self.registry()
1934 server = self.sliceapi()
1935 cred = self.my_authority_credential_string()
1936 trusted_certs = server.get_trusted_certs(cred)
1937 if not options.registry_interface:
1938 trusted_certs = ReturnValue.get_value(trusted_certs)
1940 for trusted_cert in trusted_certs:
1941 print("\n===========================================================\n")
1942 gid = GID(string=trusted_cert)
1944 cert = Certificate(string=trusted_cert)
1945 self.logger.debug('Sfi.trusted -> {}'.format(cert.get_subject()))
1946 print("Certificate:\n{}\n\n".format(trusted_cert))
1947 # xxx should analyze result
1950 @declare_command("", "")
1951 def introspect(self, options, args):
1953 If remote server supports XML-RPC instrospection API, allows
1954 to list supported methods
1956 if options.registry_interface:
1957 server = self.registry()
1959 server = self.sliceapi()
1960 results = server.serverproxy.system.listMethods()
1961 # at first sight a list here means it's fine,
1962 # and a dict suggests an error (no support for introspection?)
1963 if isinstance(results, list):
1964 results = [name for name in results if 'system.' not in name]
1966 print("== methods supported at {}".format(server.url))
1967 if 'Discover' in results:
1968 print("== has support for 'Discover' - most likely a v3")
1970 print("== has no support for 'Discover' - most likely a v2")
1971 for name in results:
1974 print("Got return of type {}, expected a list".format(type(results)))
1975 print("This suggests the remote end does not support introspection")