2 # sfi.py - basic SFA command-line client
3 # this module is also used in sfascan
17 from lxml import etree
18 from StringIO import StringIO
19 from optparse import OptionParser
20 from pprint import PrettyPrinter
21 from tempfile import mkstemp
23 from sfa.trust.certificate import Keypair, Certificate
24 from sfa.trust.gid import GID
25 from sfa.trust.credential import Credential
26 from sfa.trust.sfaticket import SfaTicket
28 from sfa.util.faults import SfaInvalidArgument
29 from sfa.util.sfalogging import sfi_logger
30 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
31 from sfa.util.config import Config
32 from sfa.util.version import version_core
33 from sfa.util.cache import Cache
34 from sfa.util.printable import printable
36 from sfa.storage.record import Record
38 from sfa.rspecs.rspec import RSpec
39 from sfa.rspecs.rspec_converter import RSpecConverter
40 from sfa.rspecs.version_manager import VersionManager
42 from sfa.client.sfaclientlib import SfaClientBootstrap
43 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
44 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
45 from sfa.client.return_value import ReturnValue
46 from sfa.client.candidates import Candidates
47 from sfa.client.manifolduploader import ManifoldUploader
51 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
52 terminal_render, filter_records
55 def display_rspec(rspec, format='rspec'):
57 tree = etree.parse(StringIO(rspec))
59 result = root.xpath("./network/site/node/hostname/text()")
60 elif format in ['ip']:
61 # The IP address is not yet part of the new RSpec
62 # so this doesn't do anything yet.
63 tree = etree.parse(StringIO(rspec))
65 result = root.xpath("./network/site/node/ipv4/text()")
72 def display_list(results):
73 for result in results:
76 def display_records(recordList, dump=False):
77 ''' Print all fields in the record'''
78 for record in recordList:
79 display_record(record, dump)
81 def display_record(record, dump=False):
83 record.dump(sort=True)
85 info = record.getdict()
86 print "%s (%s)" % (info['hrn'], info['type'])
90 def filter_records(type, records):
92 for record in records:
93 if (record['type'] == type) or (type == "all"):
94 filtered_records.append(record)
95 return filtered_records
98 def credential_printable (cred):
99 credential = Credential(cred=cred)
101 result += credential.pretty_cred()
103 rights = credential.get_privileges()
104 result += "type=%s\n" % credential.type
105 result += "version=%s\n" % credential.version
106 result += "rights=%s\n" % rights
109 def show_credentials (cred_s):
110 if not isinstance (cred_s,list): cred_s = [cred_s]
112 print "Using Credential %s"%credential_printable(cred)
115 def save_raw_to_file(var, filename, format="text", banner=None):
117 # if filename is "-", send it to stdout
120 f = open(filename, "w")
125 elif format == "pickled":
126 f.write(pickle.dumps(var))
127 elif format == "json":
128 if hasattr(json, "dumps"):
129 f.write(json.dumps(var)) # python 2.6
131 f.write(json.write(var)) # python 2.5
133 # this should never happen
134 print "unknown output format", format
136 f.write('\n'+banner+"\n")
138 def save_rspec_to_file(rspec, filename):
139 if not filename.endswith(".rspec"):
140 filename = filename + ".rspec"
141 f = open(filename, 'w')
146 def save_records_to_file(filename, record_dicts, format="xml"):
149 for record_dict in record_dicts:
151 save_record_to_file(filename + "." + str(index), record_dict)
153 save_record_to_file(filename, record_dict)
155 elif format == "xmllist":
156 f = open(filename, "w")
157 f.write("<recordlist>\n")
158 for record_dict in record_dicts:
159 record_obj=Record(dict=record_dict)
160 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
161 f.write("</recordlist>\n")
163 elif format == "hrnlist":
164 f = open(filename, "w")
165 for record_dict in record_dicts:
166 record_obj=Record(dict=record_dict)
167 f.write(record_obj.hrn + "\n")
170 # this should never happen
171 print "unknown output format", format
173 def save_record_to_file(filename, record_dict):
174 record = Record(dict=record_dict)
175 xml = record.save_as_xml()
176 f=codecs.open(filename, encoding='utf-8',mode="w")
181 # minimally check a key argument
182 def check_ssh_key (key):
183 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
184 return re.match(good_ssh_key, key, re.IGNORECASE)
187 def normalize_type (type):
188 if type.startswith('au'):
190 elif type.startswith('us'):
192 elif type.startswith('sl'):
194 elif type.startswith('no'):
199 def load_record_from_opts(options):
201 if hasattr(options, 'type'):
202 options.type = normalize_type(options.type)
203 if hasattr(options, 'xrn') and options.xrn:
204 if hasattr(options, 'type') and options.type:
205 xrn = Xrn(options.xrn, options.type)
207 xrn = Xrn(options.xrn)
208 record_dict['urn'] = xrn.get_urn()
209 record_dict['hrn'] = xrn.get_hrn()
210 record_dict['type'] = xrn.get_type()
211 if hasattr(options, 'key') and options.key:
213 pubkey = open(options.key, 'r').read()
216 if not check_ssh_key (pubkey):
217 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
218 record_dict['reg-keys'] = [pubkey]
219 if hasattr(options, 'slices') and options.slices:
220 record_dict['slices'] = options.slices
221 if hasattr(options, 'reg_researchers') and options.reg_researchers is not None:
222 record_dict['reg-researchers'] = options.reg_researchers
223 if hasattr(options, 'email') and options.email:
224 record_dict['email'] = options.email
225 # authorities can have a name for standalone deployment
226 if hasattr(options, 'name') and options.name:
227 record_dict['name'] = options.name
228 if hasattr(options, 'reg_pis') and options.reg_pis:
229 record_dict['reg-pis'] = options.reg_pis
231 # handle extra settings
232 record_dict.update(options.extras)
234 return Record(dict=record_dict)
236 def load_record_from_file(filename):
237 f=codecs.open(filename, encoding="utf-8", mode="r")
238 xml_string = f.read()
240 return Record(xml=xml_string)
244 def unique_call_id(): return uuid.uuid4().urn
246 ########## a simple model for maintaing 3 doc attributes per command (instead of just one)
247 # essentially for the methods that implement a subcommand like sfi list
248 # we need to keep track of
249 # (*) doc a few lines that tell what it does, still located in __doc__
250 # (*) args_string a simple one-liner that describes mandatory arguments
251 # (*) example well, one or several releant examples
253 # since __doc__ only accounts for one, we use this simple mechanism below
254 # however we keep doc in place for easier migration
256 from functools import wraps
258 # we use a list as well as a dict so we can keep track of the order
262 def declare_command (args_string, example,aliases=None):
264 name=getattr(m,'__name__')
265 doc=getattr(m,'__doc__',"-- missing doc --")
266 doc=doc.strip(" \t\n")
267 commands_list.append(name)
268 # last item is 'canonical' name, so we can know which commands are aliases
269 command_tuple=(doc, args_string, example,name)
270 commands_dict[name]=command_tuple
271 if aliases is not None:
272 for alias in aliases:
273 commands_list.append(alias)
274 commands_dict[alias]=command_tuple
276 def new_method (*args, **kwds): return m(*args, **kwds)
281 def remove_none_fields (record):
282 none_fields=[ k for (k,v) in record.items() if v is None ]
283 for k in none_fields: del record[k]
289 # dirty hack to make this class usable from the outside
290 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
293 def default_sfi_dir ():
294 if os.path.isfile("./sfi_config"):
297 return os.path.expanduser("~/.sfi/")
299 # dummy to meet Sfi's expectations for its 'options' field
300 # i.e. s/t we can do setattr on
304 def __init__ (self,options=None):
305 if options is None: options=Sfi.DummyOptions()
306 for opt in Sfi.required_options:
307 if not hasattr(options,opt): setattr(options,opt,None)
308 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
309 self.options = options
311 self.authority = None
312 self.logger = sfi_logger
313 self.logger.enable_console()
314 ### various auxiliary material that we keep at hand
316 # need to call this other than just 'config' as we have a command/method with that name
317 self.config_instance=None
318 self.config_file=None
319 self.client_bootstrap=None
321 ### suitable if no reasonable command has been provided
322 def print_commands_help (self, options):
323 verbose=getattr(options,'verbose')
324 format3="%10s %-35s %s"
328 print format3%("command","cmd_args","description")
332 self.create_parser_global().print_help()
333 # preserve order from the code
334 for command in commands_list:
336 (doc, args_string, example, canonical) = commands_dict[command]
338 print "Cannot find info on command %s - skipped"%command
342 if command==canonical:
343 doc=doc.replace("\n","\n"+format3offset*' ')
344 print format3%(command,args_string,doc)
346 self.create_parser_command(command).print_help()
348 print format3%(command,"<<alias for %s>>"%canonical,"")
350 ### now if a known command was found we can be more verbose on that one
351 def print_help (self):
352 print "==================== Generic sfi usage"
353 self.sfi_parser.print_help()
354 (doc,_,example,canonical)=commands_dict[self.command]
355 if canonical != self.command:
356 print "\n==================== NOTE: %s is an alias for genuine %s"%(self.command,canonical)
357 self.command=canonical
358 print "\n==================== Purpose of %s"%self.command
360 print "\n==================== Specific usage for %s"%self.command
361 self.command_parser.print_help()
363 print "\n==================== %s example(s)"%self.command
366 def create_parser_global(self):
367 # Generate command line parser
368 parser = OptionParser(add_help_option=False,
369 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
370 description="Commands: %s"%(" ".join(commands_list)))
371 parser.add_option("-r", "--registry", dest="registry",
372 help="root registry", metavar="URL", default=None)
373 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
374 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
375 parser.add_option("-R", "--raw", dest="raw", default=None,
376 help="Save raw, unparsed server response to a file")
377 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
378 help="raw file format ([text]|pickled|json)", default="text",
379 choices=("text","pickled","json"))
380 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
381 help="text string to write before and after raw output")
382 parser.add_option("-d", "--dir", dest="sfi_dir",
383 help="config & working directory - default is %default",
384 metavar="PATH", default=Sfi.default_sfi_dir())
385 parser.add_option("-u", "--user", dest="user",
386 help="user name", metavar="HRN", default=None)
387 parser.add_option("-a", "--auth", dest="auth",
388 help="authority name", metavar="HRN", default=None)
389 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
390 help="verbose mode - cumulative")
391 parser.add_option("-D", "--debug",
392 action="store_true", dest="debug", default=False,
393 help="Debug (xml-rpc) protocol messages")
394 # would it make sense to use ~/.ssh/id_rsa as a default here ?
395 parser.add_option("-k", "--private-key",
396 action="store", dest="user_private_key", default=None,
397 help="point to the private key file to use if not yet installed in sfi_dir")
398 parser.add_option("-t", "--timeout", dest="timeout", default=None,
399 help="Amout of time to wait before timing out the request")
400 parser.add_option("-h", "--help",
401 action="store_true", dest="help", default=False,
402 help="one page summary on commands & exit")
403 parser.disable_interspersed_args()
408 def create_parser_command(self, command):
409 if command not in commands_dict:
410 msg="Invalid command\n"
412 msg += ','.join(commands_list)
413 self.logger.critical(msg)
416 # retrieve args_string
417 (_, args_string, __,canonical) = commands_dict[command]
419 parser = OptionParser(add_help_option=False,
420 usage="sfi [sfi_options] %s [cmd_options] %s"
421 % (command, args_string))
422 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
423 help="Summary of one command usage")
425 if canonical in ("config"):
426 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
427 help='how myslice config variables as well')
429 if canonical in ("version"):
430 parser.add_option("-l","--local",
431 action="store_true", dest="version_local", default=False,
432 help="display version of the local client")
434 if canonical in ("version", "trusted"):
435 parser.add_option("-R","--registry_interface",
436 action="store_true", dest="registry_interface", default=False,
437 help="target the registry interface instead of slice interface")
439 if canonical in ("register", "update"):
440 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
441 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
442 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
443 parser.add_option('-n', '--name', dest='name', default="", help="name (optional for authorities)")
444 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
446 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
447 default='', type="str", action='callback', callback=optparse_listvalue_callback)
448 parser.add_option('-r', '--researchers', dest='reg_researchers', metavar='<researchers>',
449 help='Set/replace slice researchers - use -r none to reset', default=None, type="str", action='callback',
450 callback=optparse_listvalue_callback)
451 parser.add_option('-p', '--pis', dest='reg_pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
452 default='', type="str", action='callback', callback=optparse_listvalue_callback)
453 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
454 action="callback", callback=optparse_dictvalue_callback, nargs=1,
455 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
457 # user specifies remote aggregate/sm/component
458 if canonical in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
459 "action", "shutdown", "renew", "status"):
460 parser.add_option("-d", "--delegate", dest="delegate", default=None,
462 help="Include a credential delegated to the user's root"+\
463 "authority in set of credentials for this call")
465 # show_credential option
466 if canonical in ("list","resources", "describe", "provision", "allocate", "register","update","remove","delete","status","renew"):
467 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
468 help="show credential(s) used in human-readable form")
469 if canonical in ("renew"):
470 parser.add_option("-l","--as-long-as-possible",dest='alap',action='store_true',default=False,
471 help="renew as long as possible")
472 # registy filter option
473 if canonical in ("list", "show", "remove"):
474 parser.add_option("-t", "--type", dest="type", type="choice",
475 help="type filter ([all]|user|slice|authority|node|aggregate)",
476 choices=("all", "user", "slice", "authority", "node", "aggregate"),
478 if canonical in ("show"):
479 parser.add_option("-k","--key",dest="keys",action="append",default=[],
480 help="specify specific keys to be displayed from record")
481 parser.add_option("-n","--no-details",dest="no_details",action="store_true",default=False,
482 help="call Resolve without the 'details' option")
483 if canonical in ("resources", "describe"):
485 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="GENI 3",
486 help="schema type and version of resulting RSpec")
487 # disable/enable cached rspecs
488 parser.add_option("-c", "--current", dest="current", default=False,
490 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
492 parser.add_option("-f", "--format", dest="format", type="choice",
493 help="display format ([xml]|dns|ip)", default="xml",
494 choices=("xml", "dns", "ip"))
495 #panos: a new option to define the type of information about resources a user is interested in
496 parser.add_option("-i", "--info", dest="info",
497 help="optional component information", default=None)
498 # a new option to retreive or not reservation-oriented RSpecs (leases)
499 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
500 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
501 choices=("all", "resources", "leases"), default="resources")
504 if canonical in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
505 parser.add_option("-o", "--output", dest="file",
506 help="output XML to file", metavar="FILE", default=None)
508 if canonical in ("show", "list"):
509 parser.add_option("-f", "--format", dest="format", type="choice",
510 help="display format ([text]|xml)", default="text",
511 choices=("text", "xml"))
513 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
514 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
515 choices=("xml", "xmllist", "hrnlist"))
516 if canonical == 'list':
517 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
518 help="list all child records", default=False)
519 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
520 help="gives details, like user keys", default=False)
521 if canonical in ("delegate"):
522 parser.add_option("-u", "--user",
523 action="store_true", dest="delegate_user", default=False,
524 help="delegate your own credentials; default if no other option is provided")
525 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
526 metavar="slice_hrn", help="delegate cred. for slice HRN")
527 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
528 metavar='auth_hrn', help="delegate cred for auth HRN")
529 # this primarily is a shorthand for -A my_hrn^
530 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
531 help="delegate your PI credentials, so s.t. like -A your_hrn^")
532 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
533 help="""by default the mandatory argument is expected to be a user,
534 use this if you mean an authority instead""")
536 if canonical in ("myslice"):
537 parser.add_option("-p","--password",dest='password',action='store',default=None,
538 help="specify mainfold password on the command line")
539 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
540 metavar="slice_hrn", help="delegate cred. for slice HRN")
541 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
542 metavar='auth_hrn', help="delegate PI cred for auth HRN")
543 parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
544 parser.add_option('-b', '--backend', dest='backend', help="Override 'backend' from the config file")
550 # Main: parse arguments and dispatch to command
552 def dispatch(self, command, command_options, command_args):
553 (doc, args_string, example, canonical) = commands_dict[command]
554 method=getattr(self, canonical, None)
556 print "sfi: unknown command %s"%command
557 raise SystemExit,"Unknown command %s"%command
558 return method(command_options, command_args)
561 self.sfi_parser = self.create_parser_global()
562 (options, args) = self.sfi_parser.parse_args()
564 self.print_commands_help(options)
566 self.options = options
568 self.logger.setLevelFromOptVerbose(self.options.verbose)
571 self.logger.critical("No command given. Use -h for help.")
572 self.print_commands_help(options)
575 # complete / find unique match with command set
576 command_candidates = Candidates (commands_list)
578 command = command_candidates.only_match(input)
580 self.print_commands_help(options)
582 # second pass options parsing
584 self.command_parser = self.create_parser_command(command)
585 (command_options, command_args) = self.command_parser.parse_args(args[1:])
586 if command_options.help:
589 self.command_options = command_options
593 self.logger.debug("Command=%s" % self.command)
596 retcod = self.dispatch(command, command_options, command_args)
600 self.logger.log_exc ("sfi command %s failed"%command)
605 def read_config(self):
606 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
607 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
609 if Config.is_ini(config_file):
610 config = Config (config_file)
612 # try upgrading from shell config format
613 fp, fn = mkstemp(suffix='sfi_config', text=True)
615 # we need to preload the sections we want parsed
616 # from the shell config
617 config.add_section('sfi')
618 # sface users should be able to use this same file to configure their stuff
619 config.add_section('sface')
620 # manifold users should be able to specify the details
621 # of their backend server here for 'sfi myslice'
622 config.add_section('myslice')
623 config.load(config_file)
625 shutil.move(config_file, shell_config_file)
627 config.save(config_file)
630 self.logger.critical("Failed to read configuration file %s"%config_file)
631 self.logger.info("Make sure to remove the export clauses and to add quotes")
632 if self.options.verbose==0:
633 self.logger.info("Re-run with -v for more details")
635 self.logger.log_exc("Could not read config file %s"%config_file)
638 self.config_instance=config
641 if (self.options.sm is not None):
642 self.sm_url = self.options.sm
643 elif hasattr(config, "SFI_SM"):
644 self.sm_url = config.SFI_SM
646 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
650 if (self.options.registry is not None):
651 self.reg_url = self.options.registry
652 elif hasattr(config, "SFI_REGISTRY"):
653 self.reg_url = config.SFI_REGISTRY
655 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
659 if (self.options.user is not None):
660 self.user = self.options.user
661 elif hasattr(config, "SFI_USER"):
662 self.user = config.SFI_USER
664 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
668 if (self.options.auth is not None):
669 self.authority = self.options.auth
670 elif hasattr(config, "SFI_AUTH"):
671 self.authority = config.SFI_AUTH
673 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
676 self.config_file=config_file
681 # Get various credential and spec files
683 # Establishes limiting conventions
684 # - conflates MAs and SAs
685 # - assumes last token in slice name is unique
687 # Bootstraps credentials
688 # - bootstrap user credential from self-signed certificate
689 # - bootstrap authority credential from user credential
690 # - bootstrap slice credential from user credential
693 # init self-signed cert, user credentials and gid
694 def bootstrap (self):
695 if self.options.verbose:
696 self.logger.info("Initializing SfaClientBootstrap with {}".format(self.reg_url))
697 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
699 # if -k is provided, use this to initialize private key
700 if self.options.user_private_key:
701 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
703 # trigger legacy compat code if needed
704 # the name has changed from just <leaf>.pkey to <hrn>.pkey
705 if not os.path.isfile(client_bootstrap.private_key_filename()):
706 self.logger.info ("private key not found, trying legacy name")
708 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
709 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
710 client_bootstrap.init_private_key_if_missing (legacy_private_key)
711 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
713 self.logger.log_exc("Can't find private key ")
717 client_bootstrap.bootstrap_my_gid()
718 # extract what's needed
719 self.private_key = client_bootstrap.private_key()
720 self.my_credential_string = client_bootstrap.my_credential_string ()
721 self.my_credential = {'geni_type': 'geni_sfa',
723 'geni_value': self.my_credential_string}
724 self.my_gid = client_bootstrap.my_gid ()
725 self.client_bootstrap = client_bootstrap
728 def my_authority_credential_string(self):
729 if not self.authority:
730 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
732 return self.client_bootstrap.authority_credential_string (self.authority)
734 def authority_credential_string(self, auth_hrn):
735 return self.client_bootstrap.authority_credential_string (auth_hrn)
737 def slice_credential_string(self, name):
738 return self.client_bootstrap.slice_credential_string (name)
740 def slice_credential(self, name):
741 return {'geni_type': 'geni_sfa',
743 'geni_value': self.slice_credential_string(name)}
745 # xxx should be supported by sfaclientbootstrap as well
746 def delegate_cred(self, object_cred, hrn, type='authority'):
747 # the gid and hrn of the object we are delegating
748 if isinstance(object_cred, str):
749 object_cred = Credential(string=object_cred)
750 object_gid = object_cred.get_gid_object()
751 object_hrn = object_gid.get_hrn()
753 if not object_cred.get_privileges().get_all_delegate():
754 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
757 # the delegating user's gid
758 caller_gidfile = self.my_gid()
760 # the gid of the user who will be delegated to
761 delegee_gid = self.client_bootstrap.gid(hrn,type)
762 delegee_hrn = delegee_gid.get_hrn()
763 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
764 return dcred.save_to_string(save_parents=True)
767 # Management of the servers
772 if not hasattr (self, 'registry_proxy'):
773 self.logger.info("Contacting Registry at: %s"%self.reg_url)
774 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
775 timeout=self.options.timeout, verbose=self.options.debug)
776 return self.registry_proxy
780 if not hasattr (self, 'sliceapi_proxy'):
781 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
782 if hasattr(self.command_options,'component') and self.command_options.component:
783 # resolve the hrn at the registry
784 node_hrn = self.command_options.component
785 records = self.registry().Resolve(node_hrn, self.my_credential_string)
786 records = filter_records('node', records)
788 self.logger.warning("No such component:%r"% opts.component)
790 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
791 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
793 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
794 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
795 self.sm_url = 'http://' + self.sm_url
796 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
797 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
798 timeout=self.options.timeout, verbose=self.options.debug)
799 return self.sliceapi_proxy
801 def get_cached_server_version(self, server):
802 # check local cache first
805 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
806 cache_key = server.url + "-version"
808 cache = Cache(cache_file)
811 self.logger.info("Local cache not found at: %s" % cache_file)
814 version = cache.get(cache_key)
817 result = server.GetVersion()
818 version= ReturnValue.get_value(result)
819 # cache version for 20 minutes
820 cache.add(cache_key, version, ttl= 60*20)
821 self.logger.info("Updating cache file %s" % cache_file)
822 cache.save_to_file(cache_file)
826 ### resurrect this temporarily so we can support V1 aggregates for a while
827 def server_supports_options_arg(self, server):
829 Returns true if server support the optional call_id arg, false otherwise.
831 server_version = self.get_cached_server_version(server)
833 # xxx need to rewrite this
834 if int(server_version.get('geni_api')) >= 2:
838 def server_supports_call_id_arg(self, server):
839 server_version = self.get_cached_server_version(server)
841 if 'sfa' in server_version and 'code_tag' in server_version:
842 code_tag = server_version['code_tag']
843 code_tag_parts = code_tag.split("-")
844 version_parts = code_tag_parts[0].split(".")
845 major, minor = version_parts[0], version_parts[1]
846 rev = code_tag_parts[1]
847 if int(major) == 1 and minor == 0 and build >= 22:
851 ### ois = options if supported
852 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
853 def ois (self, server, option_dict):
854 if self.server_supports_options_arg (server):
856 elif self.server_supports_call_id_arg (server):
857 return [ unique_call_id () ]
861 ### cis = call_id if supported - like ois
862 def cis (self, server):
863 if self.server_supports_call_id_arg (server):
864 return [ unique_call_id ]
868 ######################################## miscell utilities
869 def get_rspec_file(self, rspec):
870 if (os.path.isabs(rspec)):
873 file = os.path.join(self.options.sfi_dir, rspec)
874 if (os.path.isfile(file)):
877 self.logger.critical("No such rspec file %s"%rspec)
880 def get_record_file(self, record):
881 if (os.path.isabs(record)):
884 file = os.path.join(self.options.sfi_dir, record)
885 if (os.path.isfile(file)):
888 self.logger.critical("No such registry record file %s"%record)
892 # helper function to analyze raw output
893 # for main : return 0 if everything is fine, something else otherwise (mostly 1 for now)
894 def success (self, raw):
895 return_value=ReturnValue (raw)
896 output=ReturnValue.get_output(return_value)
897 # means everything is fine
900 # something went wrong
901 print 'ERROR:',output
904 #==========================================================================
905 # Following functions implement the commands
907 # Registry-related commands
908 #==========================================================================
910 @declare_command("","")
911 def config (self, options, args):
912 "Display contents of current config"
913 print "# From configuration file %s"%self.config_file
914 flags=[ ('sfi', [ ('registry','reg_url'),
915 ('auth','authority'),
921 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
923 for (section, tuples) in flags:
926 for (external_name, internal_name) in tuples:
927 print "%-20s = %s"%(external_name,getattr(self,internal_name))
930 varname="%s_%s"%(section.upper(),name.upper())
931 value=getattr(self.config_instance,varname)
932 print "%-20s = %s"%(name,value)
933 # xxx should analyze result
936 @declare_command("","")
937 def version(self, options, args):
939 display an SFA server version (GetVersion)
940 or version information about sfi itself
942 if options.version_local:
943 version=version_core()
945 if options.registry_interface:
946 server=self.registry()
948 server = self.sliceapi()
949 result = server.GetVersion()
950 version = ReturnValue.get_value(result)
952 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
954 pprinter = PrettyPrinter(indent=4)
955 pprinter.pprint(version)
956 # xxx should analyze result
959 @declare_command("authority","")
960 def list(self, options, args):
962 list entries in named authority registry (List)
969 if options.recursive:
970 opts['recursive'] = options.recursive
972 if options.show_credential:
973 show_credentials(self.my_credential_string)
975 list = self.registry().List(hrn, self.my_credential_string, options)
977 raise Exception, "Not enough parameters for the 'list' command"
979 # filter on person, slice, site, node, etc.
980 # This really should be in the self.filter_records funct def comment...
981 list = filter_records(options.type, list)
982 terminal_render (list, options)
984 save_records_to_file(options.file, list, options.fileformat)
985 # xxx should analyze result
988 @declare_command("name","")
989 def show(self, options, args):
991 show details about named registry record (Resolve)
997 # explicitly require Resolve to run in details mode
999 if not options.no_details: resolve_options['details']=True
1000 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, resolve_options)
1001 record_dicts = filter_records(options.type, record_dicts)
1002 if not record_dicts:
1003 self.logger.error("No record of type %s"% options.type)
1005 # user has required to focus on some keys
1007 def project (record):
1009 for key in options.keys:
1010 try: projected[key]=record[key]
1013 record_dicts = [ project (record) for record in record_dicts ]
1014 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
1015 for record in records:
1016 if (options.format == "text"): record.dump(sort=True)
1017 else: print record.save_as_xml()
1019 save_records_to_file(options.file, record_dicts, options.fileformat)
1020 # xxx should analyze result
1023 # this historically was named 'add', it is now 'register' with an alias for legacy
1024 @declare_command("[xml-filename]","",['add'])
1025 def register(self, options, args):
1026 """create new record in registry (Register)
1027 from command line options (recommended)
1028 old-school method involving an xml file still supported"""
1030 auth_cred = self.my_authority_credential_string()
1031 if options.show_credential:
1032 show_credentials(auth_cred)
1039 record_filepath = args[0]
1040 rec_file = self.get_record_file(record_filepath)
1041 record_dict.update(load_record_from_file(rec_file).todict())
1043 print "Cannot load record file %s"%record_filepath
1046 record_dict.update(load_record_from_opts(options).todict())
1047 # we should have a type by now
1048 if 'type' not in record_dict :
1051 # this is still planetlab dependent.. as plc will whine without that
1052 # also, it's only for adding
1053 if record_dict['type'] == 'user':
1054 if not 'first_name' in record_dict:
1055 record_dict['first_name'] = record_dict['hrn']
1056 if 'last_name' not in record_dict:
1057 record_dict['last_name'] = record_dict['hrn']
1058 register = self.registry().Register(record_dict, auth_cred)
1059 # xxx looks like the result here is not ReturnValue-compatible
1060 #return self.success (register)
1061 # xxx should analyze result
1064 @declare_command("[xml-filename]","")
1065 def update(self, options, args):
1066 """update record into registry (Update)
1067 from command line options (recommended)
1068 old-school method involving an xml file still supported"""
1071 record_filepath = args[0]
1072 rec_file = self.get_record_file(record_filepath)
1073 record_dict.update(load_record_from_file(rec_file).todict())
1075 record_dict.update(load_record_from_opts(options).todict())
1076 # at the very least we need 'type' here
1077 if 'type' not in record_dict or record_dict['type'] is None:
1081 # don't translate into an object, as this would possibly distort
1082 # user-provided data; e.g. add an 'email' field to Users
1083 if record_dict['type'] in ['user']:
1084 if record_dict['hrn'] == self.user:
1085 cred = self.my_credential_string
1087 cred = self.my_authority_credential_string()
1088 elif record_dict['type'] in ['slice']:
1090 cred = self.slice_credential_string(record_dict['hrn'])
1091 except ServerException, e:
1092 # XXX smbaker -- once we have better error return codes, update this
1093 # to do something better than a string compare
1094 if "Permission error" in e.args[0]:
1095 cred = self.my_authority_credential_string()
1098 elif record_dict['type'] in ['authority']:
1099 cred = self.my_authority_credential_string()
1100 elif record_dict['type'] in ['node']:
1101 cred = self.my_authority_credential_string()
1103 raise Exception("unknown record type {}".format(record_dict['type']))
1104 if options.show_credential:
1105 show_credentials(cred)
1106 update = self.registry().Update(record_dict, cred)
1107 # xxx looks like the result here is not ReturnValue-compatible
1108 #return self.success(update)
1109 # xxx should analyze result
1112 @declare_command("hrn","")
1113 def remove(self, options, args):
1114 "remove registry record by name (Remove)"
1115 auth_cred = self.my_authority_credential_string()
1123 if options.show_credential:
1124 show_credentials(auth_cred)
1125 remove = self.registry().Remove(hrn, auth_cred, type)
1126 # xxx looks like the result here is not ReturnValue-compatible
1127 #return self.success (remove)
1128 # xxx should analyze result
1131 # ==================================================================
1132 # Slice-related commands
1133 # ==================================================================
1135 # show rspec for named slice
1136 @declare_command("","")
1137 def resources(self, options, args):
1139 discover available resources (ListResources)
1141 server = self.sliceapi()
1144 creds = [self.my_credential]
1145 if options.delegate:
1146 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1147 if options.show_credential:
1148 show_credentials(creds)
1150 # no need to check if server accepts the options argument since the options has
1151 # been a required argument since v1 API
1153 # always send call_id to v2 servers
1154 api_options ['call_id'] = unique_call_id()
1155 # ask for cached value if available
1156 api_options ['cached'] = True
1158 api_options['info'] = options.info
1159 if options.list_leases:
1160 api_options['list_leases'] = options.list_leases
1162 if options.current == True:
1163 api_options['cached'] = False
1165 api_options['cached'] = True
1166 if options.rspec_version:
1167 version_manager = VersionManager()
1168 server_version = self.get_cached_server_version(server)
1169 if 'sfa' in server_version:
1170 # just request the version the client wants
1171 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1173 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1175 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1176 list_resources = server.ListResources (creds, api_options)
1177 value = ReturnValue.get_value(list_resources)
1178 if self.options.raw:
1179 save_raw_to_file(list_resources, self.options.raw, self.options.rawformat, self.options.rawbanner)
1180 if options.file is not None:
1181 save_rspec_to_file(value, options.file)
1182 if (self.options.raw is None) and (options.file is None):
1183 display_rspec(value, options.format)
1184 return self.success(list_resources)
1186 @declare_command("slice_hrn","")
1187 def describe(self, options, args):
1189 shows currently allocated/provisioned resources
1190 of the named slice or set of slivers (Describe)
1192 server = self.sliceapi()
1195 creds = [self.slice_credential(args[0])]
1196 if options.delegate:
1197 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1198 if options.show_credential:
1199 show_credentials(creds)
1201 api_options = {'call_id': unique_call_id(),
1203 'info': options.info,
1204 'list_leases': options.list_leases,
1205 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1208 api_options['info'] = options.info
1210 if options.rspec_version:
1211 version_manager = VersionManager()
1212 server_version = self.get_cached_server_version(server)
1213 if 'sfa' in server_version:
1214 # just request the version the client wants
1215 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1217 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1218 urn = Xrn(args[0], type='slice').get_urn()
1219 remove_none_fields(api_options)
1220 describe = server.Describe([urn], creds, api_options)
1221 value = ReturnValue.get_value(describe)
1222 if self.options.raw:
1223 save_raw_to_file(describe, self.options.raw, self.options.rawformat, self.options.rawbanner)
1224 if options.file is not None:
1225 save_rspec_to_file(value['geni_rspec'], options.file)
1226 if (self.options.raw is None) and (options.file is None):
1227 display_rspec(value['geni_rspec'], options.format)
1228 return self.success (describe)
1230 @declare_command("slice_hrn [<sliver_urn>...]","")
1231 def delete(self, options, args):
1233 de-allocate and de-provision all or named slivers of the named slice (Delete)
1235 server = self.sliceapi()
1239 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1242 # we have sliver urns
1243 sliver_urns = args[1:]
1245 # we provision all the slivers of the slice
1246 sliver_urns = [slice_urn]
1249 slice_cred = self.slice_credential(slice_hrn)
1250 creds = [slice_cred]
1252 # options and call_id when supported
1254 api_options ['call_id'] = unique_call_id()
1255 if options.show_credential:
1256 show_credentials(creds)
1257 delete = server.Delete(sliver_urns, creds, *self.ois(server, api_options ) )
1258 value = ReturnValue.get_value(delete)
1259 if self.options.raw:
1260 save_raw_to_file(delete, self.options.raw, self.options.rawformat, self.options.rawbanner)
1263 return self.success (delete)
1265 @declare_command("slice_hrn rspec","")
1266 def allocate(self, options, args):
1268 allocate resources to the named slice (Allocate)
1270 server = self.sliceapi()
1271 server_version = self.get_cached_server_version(server)
1273 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1276 creds = [self.slice_credential(slice_hrn)]
1278 delegated_cred = None
1279 if server_version.get('interface') == 'slicemgr':
1280 # delegate our cred to the slice manager
1281 # do not delegate cred to slicemgr...not working at the moment
1283 #if server_version.get('hrn'):
1284 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1285 #elif server_version.get('urn'):
1286 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1288 if options.show_credential:
1289 show_credentials(creds)
1292 rspec_file = self.get_rspec_file(args[1])
1293 rspec = open(rspec_file).read()
1295 api_options ['call_id'] = unique_call_id()
1299 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1300 remove_none_fields(slice_records[0])
1301 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1302 slice_record = slice_records[0]
1303 user_hrns = slice_record['reg-researchers']
1304 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1305 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1306 sfa_users = sfa_users_arg(user_records, slice_record)
1307 geni_users = pg_users_arg(user_records)
1309 api_options['sfa_users'] = sfa_users
1310 api_options['geni_users'] = geni_users
1312 allocate = server.Allocate(slice_urn, creds, rspec, api_options)
1313 value = ReturnValue.get_value(allocate)
1314 if self.options.raw:
1315 save_raw_to_file(allocate, self.options.raw, self.options.rawformat, self.options.rawbanner)
1316 if options.file is not None:
1317 save_rspec_to_file (value['geni_rspec'], options.file)
1318 if (self.options.raw is None) and (options.file is None):
1320 return self.success(allocate)
1322 @declare_command("slice_hrn [<sliver_urn>...]","")
1323 def provision(self, options, args):
1325 provision all or named already allocated slivers of the named slice (Provision)
1327 server = self.sliceapi()
1328 server_version = self.get_cached_server_version(server)
1330 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1332 # we have sliver urns
1333 sliver_urns = args[1:]
1335 # we provision all the slivers of the slice
1336 sliver_urns = [slice_urn]
1339 creds = [self.slice_credential(slice_hrn)]
1340 delegated_cred = None
1341 if server_version.get('interface') == 'slicemgr':
1342 # delegate our cred to the slice manager
1343 # do not delegate cred to slicemgr...not working at the moment
1345 #if server_version.get('hrn'):
1346 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1347 #elif server_version.get('urn'):
1348 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1350 if options.show_credential:
1351 show_credentials(creds)
1354 api_options ['call_id'] = unique_call_id()
1356 # set the requtested rspec version
1357 version_manager = VersionManager()
1358 rspec_version = version_manager._get_version('geni', '3').to_dict()
1359 api_options['geni_rspec_version'] = rspec_version
1362 # need to pass along user keys to the aggregate.
1364 # { urn: urn:publicid:IDN+emulab.net+user+alice
1365 # keys: [<ssh key A>, <ssh key B>]
1368 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1369 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1370 slice_record = slice_records[0]
1371 user_hrns = slice_record['reg-researchers']
1372 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1373 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1374 users = pg_users_arg(user_records)
1376 api_options['geni_users'] = users
1377 provision = server.Provision(sliver_urns, creds, api_options)
1378 value = ReturnValue.get_value(provision)
1379 if self.options.raw:
1380 save_raw_to_file(provision, self.options.raw, self.options.rawformat, self.options.rawbanner)
1381 if options.file is not None:
1382 save_rspec_to_file (value['geni_rspec'], options.file)
1383 if (self.options.raw is None) and (options.file is None):
1385 return self.success(provision)
1387 @declare_command("slice_hrn","")
1388 def status(self, options, args):
1390 retrieve the status of the slivers belonging to the named slice (Status)
1392 server = self.sliceapi()
1396 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1399 slice_cred = self.slice_credential(slice_hrn)
1400 creds = [slice_cred]
1402 # options and call_id when supported
1404 api_options['call_id']=unique_call_id()
1405 if options.show_credential:
1406 show_credentials(creds)
1407 status = server.Status([slice_urn], creds, *self.ois(server,api_options))
1408 value = ReturnValue.get_value(status)
1409 if self.options.raw:
1410 save_raw_to_file(status, self.options.raw, self.options.rawformat, self.options.rawbanner)
1413 return self.success (status)
1415 @declare_command("slice_hrn [<sliver_urn>...] action","")
1416 def action(self, options, args):
1418 Perform the named operational action on all or named slivers of the named slice
1420 server = self.sliceapi()
1424 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1426 # we have sliver urns
1427 sliver_urns = args[1:-1]
1429 # we provision all the slivers of the slice
1430 sliver_urns = [slice_urn]
1433 slice_cred = self.slice_credential(args[0])
1434 creds = [slice_cred]
1435 if options.delegate:
1436 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1437 creds.append(delegated_cred)
1439 perform_action = server.PerformOperationalAction(sliver_urns, creds, action , api_options)
1440 value = ReturnValue.get_value(perform_action)
1441 if self.options.raw:
1442 save_raw_to_file(perform_action, self.options.raw, self.options.rawformat, self.options.rawbanner)
1445 return self.success (perform_action)
1447 @declare_command("slice_hrn [<sliver_urn>...] time",
1448 "\n".join(["sfi renew onelab.ple.heartbeat 2015-04-31",
1449 "sfi renew onelab.ple.heartbeat 2015-04-31T14:00:00Z",
1450 "sfi renew onelab.ple.heartbeat +5d",
1451 "sfi renew onelab.ple.heartbeat +3w",
1452 "sfi renew onelab.ple.heartbeat +2m",]))
1453 def renew(self, options, args):
1457 server = self.sliceapi()
1462 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1465 # we have sliver urns
1466 sliver_urns = args[1:-1]
1468 # we provision all the slivers of the slice
1469 sliver_urns = [slice_urn]
1470 input_time = args[-1]
1472 # time: don't try to be smart on the time format, server-side will
1474 slice_cred = self.slice_credential(args[0])
1475 creds = [slice_cred]
1476 # options and call_id when supported
1478 api_options['call_id']=unique_call_id()
1480 api_options['geni_extend_alap']=True
1481 if options.show_credential:
1482 show_credentials(creds)
1483 renew = server.Renew(sliver_urns, creds, input_time, *self.ois(server,api_options))
1484 value = ReturnValue.get_value(renew)
1485 if self.options.raw:
1486 save_raw_to_file(renew, self.options.raw, self.options.rawformat, self.options.rawbanner)
1489 return self.success(renew)
1491 @declare_command("slice_hrn","")
1492 def shutdown(self, options, args):
1494 shutdown named slice (Shutdown)
1496 server = self.sliceapi()
1499 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1501 slice_cred = self.slice_credential(slice_hrn)
1502 creds = [slice_cred]
1503 shutdown = server.Shutdown(slice_urn, creds)
1504 value = ReturnValue.get_value(shutdown)
1505 if self.options.raw:
1506 save_raw_to_file(shutdown, self.options.raw, self.options.rawformat, self.options.rawbanner)
1509 return self.success (shutdown)
1511 @declare_command("[name]","")
1512 def gid(self, options, args):
1514 Create a GID (CreateGid)
1519 target_hrn = args[0]
1520 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1521 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1523 filename = options.file
1525 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1526 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1527 GID(string=gid).save_to_file(filename)
1528 # xxx should analyze result
1531 ####################
1532 @declare_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1534 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1535 the set of credentials in the scope for this call would be
1536 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1538 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1540 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1541 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1542 because of the two -s options
1545 def delegate (self, options, args):
1547 (locally) create delegate credential for use by given hrn
1548 make sure to check for 'sfi myslice' instead if you plan
1555 # support for several delegations in the same call
1556 # so first we gather the things to do
1558 for slice_hrn in options.delegate_slices:
1559 message="%s.slice"%slice_hrn
1560 original = self.slice_credential_string(slice_hrn)
1561 tuples.append ( (message, original,) )
1562 if options.delegate_pi:
1563 my_authority=self.authority
1564 message="%s.pi"%my_authority
1565 original = self.my_authority_credential_string()
1566 tuples.append ( (message, original,) )
1567 for auth_hrn in options.delegate_auths:
1568 message="%s.auth"%auth_hrn
1569 original=self.authority_credential_string(auth_hrn)
1570 tuples.append ( (message, original, ) )
1571 # if nothing was specified at all at this point, let's assume -u
1572 if not tuples: options.delegate_user=True
1574 if options.delegate_user:
1575 message="%s.user"%self.user
1576 original = self.my_credential_string
1577 tuples.append ( (message, original, ) )
1579 # default type for beneficial is user unless -A
1580 if options.delegate_to_authority: to_type='authority'
1581 else: to_type='user'
1583 # let's now handle all this
1584 # it's all in the filenaming scheme
1585 for (message,original) in tuples:
1586 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1587 delegated_credential = Credential (string=delegated_string)
1588 filename = os.path.join ( self.options.sfi_dir,
1589 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1590 delegated_credential.save_to_file(filename, save_parents=True)
1591 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1593 ####################
1594 @declare_command("","""$ less +/myslice sfi_config
1596 backend = http://manifold.pl.sophia.inria.fr:7080
1597 # the HRN that myslice uses, so that we are delegating to
1598 delegate = ple.upmc.slicebrowser
1599 # platform - this is a myslice concept
1601 # username - as of this writing (May 2013) a simple login name
1605 will first collect the slices that you are part of, then make sure
1606 all your credentials are up-to-date (read: refresh expired ones)
1607 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1608 and upload them all on myslice backend, using 'platform' and 'user'.
1609 A password will be prompted for the upload part.
1611 $ sfi -v myslice -- or sfi -vv myslice
1612 same but with more and more verbosity
1614 $ sfi m -b http://mymanifold.foo.com:7080/
1615 is synonym to sfi myslice as no other command starts with an 'm'
1616 and uses a custom backend for this one call
1619 def myslice (self, options, args):
1621 """ This helper is for refreshing your credentials at myslice; it will
1622 * compute all the slices that you currently have credentials on
1623 * refresh all your credentials (you as a user and pi, your slices)
1624 * upload them to the manifold backend server
1625 for last phase, sfi_config is read to look for the [myslice] section,
1626 and namely the 'backend', 'delegate' and 'user' settings"""
1632 # enable info by default
1633 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1634 ### the rough sketch goes like this
1635 # (0) produce a p12 file
1636 self.client_bootstrap.my_pkcs12()
1638 # (a) rain check for sufficient config in sfi_config
1640 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1641 for key in myslice_keys:
1643 # oct 2013 - I'm finding myself juggling with config files
1644 # so a couple of command-line options can now override config
1645 if hasattr(options,key) and getattr(options,key) is not None:
1646 value=getattr(options,key)
1648 full_key="MYSLICE_" + key.upper()
1649 value=getattr(self.config_instance,full_key,None)
1650 if value: myslice_dict[key]=value
1651 else: print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1652 if len(myslice_dict) != len(myslice_keys):
1655 # (b) figure whether we are PI for the authority where we belong
1656 self.logger.info("Resolving our own id %s"%self.user)
1657 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1658 if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1659 my_record=my_records[0]
1660 my_auths_all = my_record['reg-pi-authorities']
1661 self.logger.info("Found %d authorities that we are PI for"%len(my_auths_all))
1662 self.logger.debug("They are %s"%(my_auths_all))
1664 my_auths = my_auths_all
1665 if options.delegate_auths:
1666 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1667 self.logger.debug("Restricted to user-provided auths"%(my_auths))
1669 # (c) get the set of slices that we are in
1670 my_slices_all=my_record['reg-slices']
1671 self.logger.info("Found %d slices that we are member of"%len(my_slices_all))
1672 self.logger.debug("They are: %s"%(my_slices_all))
1674 my_slices = my_slices_all
1675 # if user provided slices, deal only with these - if they are found
1676 if options.delegate_slices:
1677 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1678 self.logger.debug("Restricted to user-provided slices: %s"%(my_slices))
1680 # (d) make sure we have *valid* credentials for all these
1682 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1683 for auth_hrn in my_auths:
1684 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1685 for slice_hrn in my_slices:
1686 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1688 # (e) check for the delegated version of these
1689 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1690 # switch to myslice using an authority instead of a user
1691 delegatee_type='user'
1692 delegatee_hrn=myslice_dict['delegate']
1693 hrn_delegated_credentials = []
1694 for (hrn, htype, credential) in hrn_credentials:
1695 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1696 # save these so user can monitor what she's uploaded
1697 filename = os.path.join ( self.options.sfi_dir,
1698 "%s.%s_for_%s.%s.cred"%(hrn,htype,delegatee_hrn,delegatee_type))
1699 with file(filename,'w') as f:
1700 f.write(delegated_credential)
1701 self.logger.debug("(Over)wrote %s"%filename)
1702 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1704 # (f) and finally upload them to manifold server
1705 # xxx todo add an option so the password can be set on the command line
1706 # (but *NOT* in the config file) so other apps can leverage this
1707 self.logger.info("Uploading on backend at %s"%myslice_dict['backend'])
1708 uploader = ManifoldUploader (logger=self.logger,
1709 url=myslice_dict['backend'],
1710 platform=myslice_dict['platform'],
1711 username=myslice_dict['username'],
1712 password=options.password)
1713 uploader.prompt_all()
1714 (count_all,count_success)=(0,0)
1715 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1717 inspect=Credential(string=delegated_credential)
1718 expire_datetime=inspect.get_expiration()
1719 message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
1720 if uploader.upload(delegated_credential,message=message):
1723 self.logger.info("Successfully uploaded %d/%d credentials"%(count_success,count_all))
1725 # at first I thought we would want to save these,
1726 # like 'sfi delegate does' but on second thought
1727 # it is probably not helpful as people would not
1728 # need to run 'sfi delegate' at all anymore
1729 if count_success != count_all: sys.exit(1)
1730 # xxx should analyze result
1733 @declare_command("cred","")
1734 def trusted(self, options, args):
1736 return the trusted certs at this interface (get_trusted_certs)
1738 if options.registry_interface:
1739 server=self.registry()
1741 server = self.sliceapi()
1742 cred = self.my_authority_credential_string()
1743 trusted_certs = server.get_trusted_certs(cred)
1744 if not options.registry_interface:
1745 trusted_certs = ReturnValue.get_value(trusted_certs)
1747 for trusted_cert in trusted_certs:
1748 print "\n===========================================================\n"
1749 gid = GID(string=trusted_cert)
1751 cert = Certificate(string=trusted_cert)
1752 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1753 print "Certificate:\n%s\n\n"%trusted_cert
1754 # xxx should analyze result