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.get_summary_tostring()
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 load_record_from_opts(options):
189 if hasattr(options, 'xrn') and options.xrn:
190 if hasattr(options, 'type') and options.type:
191 xrn = Xrn(options.xrn, options.type)
193 xrn = Xrn(options.xrn)
194 record_dict['urn'] = xrn.get_urn()
195 record_dict['hrn'] = xrn.get_hrn()
196 record_dict['type'] = xrn.get_type()
197 if hasattr(options, 'key') and options.key:
199 pubkey = open(options.key, 'r').read()
202 if not check_ssh_key (pubkey):
203 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
204 record_dict['keys'] = [pubkey]
205 if hasattr(options, 'slices') and options.slices:
206 record_dict['slices'] = options.slices
207 if hasattr(options, 'reg_researchers') and options.reg_researchers is not None:
208 record_dict['reg-researchers'] = options.reg_researchers
209 if hasattr(options, 'email') and options.email:
210 record_dict['email'] = options.email
211 if hasattr(options, 'reg_pis') and options.reg_pis:
212 record_dict['reg-pis'] = options.reg_pis
214 # handle extra settings
215 record_dict.update(options.extras)
217 return Record(dict=record_dict)
219 def load_record_from_file(filename):
220 f=codecs.open(filename, encoding="utf-8", mode="r")
221 xml_string = f.read()
223 return Record(xml=xml_string)
227 def unique_call_id(): return uuid.uuid4().urn
229 ########## a simple model for maintaing 3 doc attributes per command (instead of just one)
230 # essentially for the methods that implement a subcommand like sfi list
231 # we need to keep track of
232 # (*) doc a few lines that tell what it does, still located in __doc__
233 # (*) args_string a simple one-liner that describes mandatory arguments
234 # (*) example well, one or several releant examples
236 # since __doc__ only accounts for one, we use this simple mechanism below
237 # however we keep doc in place for easier migration
239 from functools import wraps
241 # we use a list as well as a dict so we can keep track of the order
245 def declare_command (args_string, example,aliases=None):
247 name=getattr(m,'__name__')
248 doc=getattr(m,'__doc__',"-- missing doc --")
249 doc=doc.strip(" \t\n")
250 commands_list.append(name)
251 # last item is 'canonical' name, so we can know which commands are aliases
252 command_tuple=(doc, args_string, example,name)
253 commands_dict[name]=command_tuple
254 if aliases is not None:
255 for alias in aliases:
256 commands_list.append(alias)
257 commands_dict[alias]=command_tuple
259 def new_method (*args, **kwds): return m(*args, **kwds)
264 def remove_none_fields (record):
265 none_fields=[ k for (k,v) in record.items() if v is None ]
266 for k in none_fields: del record[k]
272 # dirty hack to make this class usable from the outside
273 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
276 def default_sfi_dir ():
277 if os.path.isfile("./sfi_config"):
280 return os.path.expanduser("~/.sfi/")
282 # dummy to meet Sfi's expectations for its 'options' field
283 # i.e. s/t we can do setattr on
287 def __init__ (self,options=None):
288 if options is None: options=Sfi.DummyOptions()
289 for opt in Sfi.required_options:
290 if not hasattr(options,opt): setattr(options,opt,None)
291 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
292 self.options = options
294 self.authority = None
295 self.logger = sfi_logger
296 self.logger.enable_console()
297 ### various auxiliary material that we keep at hand
299 # need to call this other than just 'config' as we have a command/method with that name
300 self.config_instance=None
301 self.config_file=None
302 self.client_bootstrap=None
304 ### suitable if no reasonable command has been provided
305 def print_commands_help (self, options):
306 verbose=getattr(options,'verbose')
307 format3="%10s %-35s %s"
311 print format3%("command","cmd_args","description")
315 self.create_parser_global().print_help()
316 # preserve order from the code
317 for command in commands_list:
319 (doc, args_string, example, canonical) = commands_dict[command]
321 print "Cannot find info on command %s - skipped"%command
325 if command==canonical:
326 doc=doc.replace("\n","\n"+format3offset*' ')
327 print format3%(command,args_string,doc)
329 self.create_parser_command(command).print_help()
331 print format3%(command,"<<alias for %s>>"%canonical,"")
333 ### now if a known command was found we can be more verbose on that one
334 def print_help (self):
335 print "==================== Generic sfi usage"
336 self.sfi_parser.print_help()
337 (doc,_,example,canonical)=commands_dict[self.command]
338 if canonical != self.command:
339 print "\n==================== NOTE: %s is an alias for genuine %s"%(self.command,canonical)
340 self.command=canonical
341 print "\n==================== Purpose of %s"%self.command
343 print "\n==================== Specific usage for %s"%self.command
344 self.command_parser.print_help()
346 print "\n==================== %s example(s)"%self.command
349 def create_parser_global(self):
350 # Generate command line parser
351 parser = OptionParser(add_help_option=False,
352 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
353 description="Commands: %s"%(" ".join(commands_list)))
354 parser.add_option("-r", "--registry", dest="registry",
355 help="root registry", metavar="URL", default=None)
356 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
357 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
358 parser.add_option("-R", "--raw", dest="raw", default=None,
359 help="Save raw, unparsed server response to a file")
360 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
361 help="raw file format ([text]|pickled|json)", default="text",
362 choices=("text","pickled","json"))
363 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
364 help="text string to write before and after raw output")
365 parser.add_option("-d", "--dir", dest="sfi_dir",
366 help="config & working directory - default is %default",
367 metavar="PATH", default=Sfi.default_sfi_dir())
368 parser.add_option("-u", "--user", dest="user",
369 help="user name", metavar="HRN", default=None)
370 parser.add_option("-a", "--auth", dest="auth",
371 help="authority name", metavar="HRN", default=None)
372 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
373 help="verbose mode - cumulative")
374 parser.add_option("-D", "--debug",
375 action="store_true", dest="debug", default=False,
376 help="Debug (xml-rpc) protocol messages")
377 # would it make sense to use ~/.ssh/id_rsa as a default here ?
378 parser.add_option("-k", "--private-key",
379 action="store", dest="user_private_key", default=None,
380 help="point to the private key file to use if not yet installed in sfi_dir")
381 parser.add_option("-t", "--timeout", dest="timeout", default=None,
382 help="Amout of time to wait before timing out the request")
383 parser.add_option("-h", "--help",
384 action="store_true", dest="help", default=False,
385 help="one page summary on commands & exit")
386 parser.disable_interspersed_args()
391 def create_parser_command(self, command):
392 if command not in commands_dict:
393 msg="Invalid command\n"
395 msg += ','.join(commands_list)
396 self.logger.critical(msg)
399 # retrieve args_string
400 (_, args_string, __,canonical) = commands_dict[command]
402 parser = OptionParser(add_help_option=False,
403 usage="sfi [sfi_options] %s [cmd_options] %s"
404 % (command, args_string))
405 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
406 help="Summary of one command usage")
408 if canonical in ("config"):
409 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
410 help='how myslice config variables as well')
412 if canonical in ("version"):
413 parser.add_option("-l","--local",
414 action="store_true", dest="version_local", default=False,
415 help="display version of the local client")
417 if canonical in ("version", "trusted"):
418 parser.add_option("-R","--registry_interface",
419 action="store_true", dest="registry_interface", default=False,
420 help="target the registry interface instead of slice interface")
422 if canonical in ("register", "update"):
423 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
424 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
425 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
426 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
428 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
429 default='', type="str", action='callback', callback=optparse_listvalue_callback)
430 parser.add_option('-r', '--researchers', dest='reg_researchers', metavar='<researchers>',
431 help='Set/replace slice researchers - use -r none to reset', default=None, type="str", action='callback',
432 callback=optparse_listvalue_callback)
433 parser.add_option('-p', '--pis', dest='reg_pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
434 default='', type="str", action='callback', callback=optparse_listvalue_callback)
435 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
436 action="callback", callback=optparse_dictvalue_callback, nargs=1,
437 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
439 # user specifies remote aggregate/sm/component
440 if canonical in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
441 "action", "shutdown", "renew", "status"):
442 parser.add_option("-d", "--delegate", dest="delegate", default=None,
444 help="Include a credential delegated to the user's root"+\
445 "authority in set of credentials for this call")
447 # show_credential option
448 if canonical in ("list","resources", "describe", "provision", "allocate", "register","update","remove","delete","status","renew"):
449 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
450 help="show credential(s) used in human-readable form")
451 # registy filter option
452 if canonical in ("list", "show", "remove"):
453 parser.add_option("-t", "--type", dest="type", type="choice",
454 help="type filter ([all]|user|slice|authority|node|aggregate)",
455 choices=("all", "user", "slice", "authority", "node", "aggregate"),
457 if canonical in ("show"):
458 parser.add_option("-k","--key",dest="keys",action="append",default=[],
459 help="specify specific keys to be displayed from record")
460 parser.add_option("-n","--no-details",dest="no_details",action="store_true",default=False,
461 help="call Resolve without the 'details' option")
462 if canonical in ("resources", "describe"):
464 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="GENI 3",
465 help="schema type and version of resulting RSpec")
466 # disable/enable cached rspecs
467 parser.add_option("-c", "--current", dest="current", default=False,
469 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
471 parser.add_option("-f", "--format", dest="format", type="choice",
472 help="display format ([xml]|dns|ip)", default="xml",
473 choices=("xml", "dns", "ip"))
474 #panos: a new option to define the type of information about resources a user is interested in
475 parser.add_option("-i", "--info", dest="info",
476 help="optional component information", default=None)
477 # a new option to retreive or not reservation-oriented RSpecs (leases)
478 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
479 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
480 choices=("all", "resources", "leases"), default="resources")
483 if canonical in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
484 parser.add_option("-o", "--output", dest="file",
485 help="output XML to file", metavar="FILE", default=None)
487 if canonical in ("show", "list"):
488 parser.add_option("-f", "--format", dest="format", type="choice",
489 help="display format ([text]|xml)", default="text",
490 choices=("text", "xml"))
492 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
493 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
494 choices=("xml", "xmllist", "hrnlist"))
495 if canonical == 'list':
496 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
497 help="list all child records", default=False)
498 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
499 help="gives details, like user keys", default=False)
500 if canonical in ("delegate"):
501 parser.add_option("-u", "--user",
502 action="store_true", dest="delegate_user", default=False,
503 help="delegate your own credentials; default if no other option is provided")
504 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
505 metavar="slice_hrn", help="delegate cred. for slice HRN")
506 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
507 metavar='auth_hrn', help="delegate cred for auth HRN")
508 # this primarily is a shorthand for -A my_hrn^
509 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
510 help="delegate your PI credentials, so s.t. like -A your_hrn^")
511 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
512 help="""by default the mandatory argument is expected to be a user,
513 use this if you mean an authority instead""")
515 if canonical in ("myslice"):
516 parser.add_option("-p","--password",dest='password',action='store',default=None,
517 help="specify mainfold password on the command line")
518 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
519 metavar="slice_hrn", help="delegate cred. for slice HRN")
520 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
521 metavar='auth_hrn', help="delegate PI cred for auth HRN")
522 parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
523 parser.add_option('-b', '--backend', dest='backend', help="Override 'backend' from the config file")
529 # Main: parse arguments and dispatch to command
531 def dispatch(self, command, command_options, command_args):
532 (doc, args_string, example, canonical) = commands_dict[command]
533 method=getattr(self, canonical, None)
535 print "sfi: unknown command %s"%command
536 raise SystemExit,"Unknown command %s"%command
537 return method(command_options, command_args)
540 self.sfi_parser = self.create_parser_global()
541 (options, args) = self.sfi_parser.parse_args()
543 self.print_commands_help(options)
545 self.options = options
547 self.logger.setLevelFromOptVerbose(self.options.verbose)
550 self.logger.critical("No command given. Use -h for help.")
551 self.print_commands_help(options)
554 # complete / find unique match with command set
555 command_candidates = Candidates (commands_list)
557 command = command_candidates.only_match(input)
559 self.print_commands_help(options)
561 # second pass options parsing
563 self.command_parser = self.create_parser_command(command)
564 (command_options, command_args) = self.command_parser.parse_args(args[1:])
565 if command_options.help:
568 self.command_options = command_options
572 self.logger.debug("Command=%s" % self.command)
575 self.dispatch(command, command_options, command_args)
579 self.logger.log_exc ("sfi command %s failed"%command)
585 def read_config(self):
586 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
587 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
589 if Config.is_ini(config_file):
590 config = Config (config_file)
592 # try upgrading from shell config format
593 fp, fn = mkstemp(suffix='sfi_config', text=True)
595 # we need to preload the sections we want parsed
596 # from the shell config
597 config.add_section('sfi')
598 # sface users should be able to use this same file to configure their stuff
599 config.add_section('sface')
600 # manifold users should be able to specify the details
601 # of their backend server here for 'sfi myslice'
602 config.add_section('myslice')
603 config.load(config_file)
605 shutil.move(config_file, shell_config_file)
607 config.save(config_file)
610 self.logger.critical("Failed to read configuration file %s"%config_file)
611 self.logger.info("Make sure to remove the export clauses and to add quotes")
612 if self.options.verbose==0:
613 self.logger.info("Re-run with -v for more details")
615 self.logger.log_exc("Could not read config file %s"%config_file)
618 self.config_instance=config
621 if (self.options.sm is not None):
622 self.sm_url = self.options.sm
623 elif hasattr(config, "SFI_SM"):
624 self.sm_url = config.SFI_SM
626 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
630 if (self.options.registry is not None):
631 self.reg_url = self.options.registry
632 elif hasattr(config, "SFI_REGISTRY"):
633 self.reg_url = config.SFI_REGISTRY
635 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
639 if (self.options.user is not None):
640 self.user = self.options.user
641 elif hasattr(config, "SFI_USER"):
642 self.user = config.SFI_USER
644 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
648 if (self.options.auth is not None):
649 self.authority = self.options.auth
650 elif hasattr(config, "SFI_AUTH"):
651 self.authority = config.SFI_AUTH
653 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
656 self.config_file=config_file
661 # Get various credential and spec files
663 # Establishes limiting conventions
664 # - conflates MAs and SAs
665 # - assumes last token in slice name is unique
667 # Bootstraps credentials
668 # - bootstrap user credential from self-signed certificate
669 # - bootstrap authority credential from user credential
670 # - bootstrap slice credential from user credential
673 # init self-signed cert, user credentials and gid
674 def bootstrap (self):
675 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
677 # if -k is provided, use this to initialize private key
678 if self.options.user_private_key:
679 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
681 # trigger legacy compat code if needed
682 # the name has changed from just <leaf>.pkey to <hrn>.pkey
683 if not os.path.isfile(client_bootstrap.private_key_filename()):
684 self.logger.info ("private key not found, trying legacy name")
686 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
687 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
688 client_bootstrap.init_private_key_if_missing (legacy_private_key)
689 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
691 self.logger.log_exc("Can't find private key ")
695 client_bootstrap.bootstrap_my_gid()
696 # extract what's needed
697 self.private_key = client_bootstrap.private_key()
698 self.my_credential_string = client_bootstrap.my_credential_string ()
699 self.my_credential = {'geni_type': 'geni_sfa',
701 'geni_value': self.my_credential_string}
702 self.my_gid = client_bootstrap.my_gid ()
703 self.client_bootstrap = client_bootstrap
706 def my_authority_credential_string(self):
707 if not self.authority:
708 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
710 return self.client_bootstrap.authority_credential_string (self.authority)
712 def authority_credential_string(self, auth_hrn):
713 return self.client_bootstrap.authority_credential_string (auth_hrn)
715 def slice_credential_string(self, name):
716 return self.client_bootstrap.slice_credential_string (name)
718 def slice_credential(self, name):
719 return {'geni_type': 'geni_sfa',
721 'geni_value': self.slice_credential_string(name)}
723 # xxx should be supported by sfaclientbootstrap as well
724 def delegate_cred(self, object_cred, hrn, type='authority'):
725 # the gid and hrn of the object we are delegating
726 if isinstance(object_cred, str):
727 object_cred = Credential(string=object_cred)
728 object_gid = object_cred.get_gid_object()
729 object_hrn = object_gid.get_hrn()
731 if not object_cred.get_privileges().get_all_delegate():
732 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
735 # the delegating user's gid
736 caller_gidfile = self.my_gid()
738 # the gid of the user who will be delegated to
739 delegee_gid = self.client_bootstrap.gid(hrn,type)
740 delegee_hrn = delegee_gid.get_hrn()
741 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
742 return dcred.save_to_string(save_parents=True)
745 # Management of the servers
750 if not hasattr (self, 'registry_proxy'):
751 self.logger.info("Contacting Registry at: %s"%self.reg_url)
752 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
753 timeout=self.options.timeout, verbose=self.options.debug)
754 return self.registry_proxy
758 if not hasattr (self, 'sliceapi_proxy'):
759 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
760 if hasattr(self.command_options,'component') and self.command_options.component:
761 # resolve the hrn at the registry
762 node_hrn = self.command_options.component
763 records = self.registry().Resolve(node_hrn, self.my_credential_string)
764 records = filter_records('node', records)
766 self.logger.warning("No such component:%r"% opts.component)
768 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
769 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
771 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
772 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
773 self.sm_url = 'http://' + self.sm_url
774 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
775 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
776 timeout=self.options.timeout, verbose=self.options.debug)
777 return self.sliceapi_proxy
779 def get_cached_server_version(self, server):
780 # check local cache first
783 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
784 cache_key = server.url + "-version"
786 cache = Cache(cache_file)
789 self.logger.info("Local cache not found at: %s" % cache_file)
792 version = cache.get(cache_key)
795 result = server.GetVersion()
796 version= ReturnValue.get_value(result)
797 # cache version for 20 minutes
798 cache.add(cache_key, version, ttl= 60*20)
799 self.logger.info("Updating cache file %s" % cache_file)
800 cache.save_to_file(cache_file)
804 ### resurrect this temporarily so we can support V1 aggregates for a while
805 def server_supports_options_arg(self, server):
807 Returns true if server support the optional call_id arg, false otherwise.
809 server_version = self.get_cached_server_version(server)
811 # xxx need to rewrite this
812 if int(server_version.get('geni_api')) >= 2:
816 def server_supports_call_id_arg(self, server):
817 server_version = self.get_cached_server_version(server)
819 if 'sfa' in server_version and 'code_tag' in server_version:
820 code_tag = server_version['code_tag']
821 code_tag_parts = code_tag.split("-")
822 version_parts = code_tag_parts[0].split(".")
823 major, minor = version_parts[0], version_parts[1]
824 rev = code_tag_parts[1]
825 if int(major) == 1 and minor == 0 and build >= 22:
829 ### ois = options if supported
830 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
831 def ois (self, server, option_dict):
832 if self.server_supports_options_arg (server):
834 elif self.server_supports_call_id_arg (server):
835 return [ unique_call_id () ]
839 ### cis = call_id if supported - like ois
840 def cis (self, server):
841 if self.server_supports_call_id_arg (server):
842 return [ unique_call_id ]
846 ######################################## miscell utilities
847 def get_rspec_file(self, rspec):
848 if (os.path.isabs(rspec)):
851 file = os.path.join(self.options.sfi_dir, rspec)
852 if (os.path.isfile(file)):
855 self.logger.critical("No such rspec file %s"%rspec)
858 def get_record_file(self, record):
859 if (os.path.isabs(record)):
862 file = os.path.join(self.options.sfi_dir, record)
863 if (os.path.isfile(file)):
866 self.logger.critical("No such registry record file %s"%record)
870 #==========================================================================
871 # Following functions implement the commands
873 # Registry-related commands
874 #==========================================================================
876 @declare_command("","")
877 def config (self, options, args):
878 "Display contents of current config"
879 print "# From configuration file %s"%self.config_file
880 flags=[ ('sfi', [ ('registry','reg_url'),
881 ('auth','authority'),
887 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
889 for (section, tuples) in flags:
892 for (external_name, internal_name) in tuples:
893 print "%-20s = %s"%(external_name,getattr(self,internal_name))
896 varname="%s_%s"%(section.upper(),name.upper())
897 value=getattr(self.config_instance,varname)
898 print "%-20s = %s"%(name,value)
900 @declare_command("","")
901 def version(self, options, args):
903 display an SFA server version (GetVersion)
904 or version information about sfi itself
906 if options.version_local:
907 version=version_core()
909 if options.registry_interface:
910 server=self.registry()
912 server = self.sliceapi()
913 result = server.GetVersion()
914 version = ReturnValue.get_value(result)
916 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
918 pprinter = PrettyPrinter(indent=4)
919 pprinter.pprint(version)
921 @declare_command("authority","")
922 def list(self, options, args):
924 list entries in named authority registry (List)
931 if options.recursive:
932 opts['recursive'] = options.recursive
934 if options.show_credential:
935 show_credentials(self.my_credential_string)
937 list = self.registry().List(hrn, self.my_credential_string, options)
939 raise Exception, "Not enough parameters for the 'list' command"
941 # filter on person, slice, site, node, etc.
942 # This really should be in the self.filter_records funct def comment...
943 list = filter_records(options.type, list)
944 terminal_render (list, options)
946 save_records_to_file(options.file, list, options.fileformat)
949 @declare_command("name","")
950 def show(self, options, args):
952 show details about named registry record (Resolve)
958 # explicitly require Resolve to run in details mode
960 if not options.no_details: resolve_options['details']=True
961 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, resolve_options)
962 record_dicts = filter_records(options.type, record_dicts)
964 self.logger.error("No record of type %s"% options.type)
966 # user has required to focus on some keys
968 def project (record):
970 for key in options.keys:
971 try: projected[key]=record[key]
974 record_dicts = [ project (record) for record in record_dicts ]
975 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
976 for record in records:
977 if (options.format == "text"): record.dump(sort=True)
978 else: print record.save_as_xml()
980 save_records_to_file(options.file, record_dicts, options.fileformat)
983 # this historically was named 'add', it is now 'register' with an alias for legacy
984 @declare_command("[xml-filename]","",['add'])
985 def register(self, options, args):
986 """create new record in registry (Register)
987 from command line options (recommended)
988 old-school method involving an xml file still supported"""
990 auth_cred = self.my_authority_credential_string()
991 if options.show_credential:
992 show_credentials(auth_cred)
999 record_filepath = args[0]
1000 rec_file = self.get_record_file(record_filepath)
1001 record_dict.update(load_record_from_file(rec_file).todict())
1003 print "Cannot load record file %s"%record_filepath
1006 record_dict.update(load_record_from_opts(options).todict())
1007 # we should have a type by now
1008 if 'type' not in record_dict :
1011 # this is still planetlab dependent.. as plc will whine without that
1012 # also, it's only for adding
1013 if record_dict['type'] == 'user':
1014 if not 'first_name' in record_dict:
1015 record_dict['first_name'] = record_dict['hrn']
1016 if 'last_name' not in record_dict:
1017 record_dict['last_name'] = record_dict['hrn']
1018 return self.registry().Register(record_dict, auth_cred)
1020 @declare_command("[xml-filename]","")
1021 def update(self, options, args):
1022 """update record into registry (Update)
1023 from command line options (recommended)
1024 old-school method involving an xml file still supported"""
1027 record_filepath = args[0]
1028 rec_file = self.get_record_file(record_filepath)
1029 record_dict.update(load_record_from_file(rec_file).todict())
1031 record_dict.update(load_record_from_opts(options).todict())
1032 # at the very least we need 'type' here
1033 if 'type' not in record_dict:
1037 # don't translate into an object, as this would possibly distort
1038 # user-provided data; e.g. add an 'email' field to Users
1039 if record_dict['type'] in ['user']:
1040 if record_dict['hrn'] == self.user:
1041 cred = self.my_credential_string
1043 cred = self.my_authority_credential_string()
1044 elif record_dict['type'] in ['slice']:
1046 cred = self.slice_credential_string(record_dict['hrn'])
1047 except ServerException, e:
1048 # XXX smbaker -- once we have better error return codes, update this
1049 # to do something better than a string compare
1050 if "Permission error" in e.args[0]:
1051 cred = self.my_authority_credential_string()
1054 elif record_dict['type'] in ['authority']:
1055 cred = self.my_authority_credential_string()
1056 elif record_dict['type'] in ['node']:
1057 cred = self.my_authority_credential_string()
1059 raise "unknown record type" + record_dict['type']
1060 if options.show_credential:
1061 show_credentials(cred)
1062 return self.registry().Update(record_dict, cred)
1064 @declare_command("hrn","")
1065 def remove(self, options, args):
1066 "remove registry record by name (Remove)"
1067 auth_cred = self.my_authority_credential_string()
1075 if options.show_credential:
1076 show_credentials(auth_cred)
1077 return self.registry().Remove(hrn, auth_cred, type)
1079 # ==================================================================
1080 # Slice-related commands
1081 # ==================================================================
1083 # show rspec for named slice
1084 @declare_command("","")
1085 def resources(self, options, args):
1087 discover available resources (ListResources)
1089 server = self.sliceapi()
1092 creds = [self.my_credential]
1093 if options.delegate:
1094 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1095 if options.show_credential:
1096 show_credentials(creds)
1098 # no need to check if server accepts the options argument since the options has
1099 # been a required argument since v1 API
1101 # always send call_id to v2 servers
1102 api_options ['call_id'] = unique_call_id()
1103 # ask for cached value if available
1104 api_options ['cached'] = True
1106 api_options['info'] = options.info
1107 if options.list_leases:
1108 api_options['list_leases'] = options.list_leases
1110 if options.current == True:
1111 api_options['cached'] = False
1113 api_options['cached'] = True
1114 if options.rspec_version:
1115 version_manager = VersionManager()
1116 server_version = self.get_cached_server_version(server)
1117 if 'sfa' in server_version:
1118 # just request the version the client wants
1119 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1121 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1123 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1124 result = server.ListResources (creds, api_options)
1125 value = ReturnValue.get_value(result)
1126 if self.options.raw:
1127 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1128 if options.file is not None:
1129 save_rspec_to_file(value, options.file)
1130 if (self.options.raw is None) and (options.file is None):
1131 display_rspec(value, options.format)
1135 @declare_command("slice_hrn","")
1136 def describe(self, options, args):
1138 shows currently allocated/provisioned resources
1139 of the named slice or set of slivers (Describe)
1141 server = self.sliceapi()
1144 creds = [self.slice_credential(args[0])]
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 api_options = {'call_id': unique_call_id(),
1152 'info': options.info,
1153 'list_leases': options.list_leases,
1154 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1157 api_options['info'] = options.info
1159 if options.rspec_version:
1160 version_manager = VersionManager()
1161 server_version = self.get_cached_server_version(server)
1162 if 'sfa' in server_version:
1163 # just request the version the client wants
1164 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1166 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1167 urn = Xrn(args[0], type='slice').get_urn()
1168 remove_none_fields(api_options)
1169 result = server.Describe([urn], creds, api_options)
1170 value = ReturnValue.get_value(result)
1171 if self.options.raw:
1172 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1173 if options.file is not None:
1174 save_rspec_to_file(value['geni_rspec'], options.file)
1175 if (self.options.raw is None) and (options.file is None):
1176 display_rspec(value, options.format)
1180 @declare_command("slice_hrn [<sliver_urn>...]","")
1181 def delete(self, options, args):
1183 de-allocate and de-provision all or named slivers of the named slice (Delete)
1185 server = self.sliceapi()
1189 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1192 # we have sliver urns
1193 sliver_urns = args[1:]
1195 # we provision all the slivers of the slice
1196 sliver_urns = [slice_urn]
1199 slice_cred = self.slice_credential(slice_hrn)
1200 creds = [slice_cred]
1202 # options and call_id when supported
1204 api_options ['call_id'] = unique_call_id()
1205 if options.show_credential:
1206 show_credentials(creds)
1207 result = server.Delete(sliver_urns, creds, *self.ois(server, api_options ) )
1208 value = ReturnValue.get_value(result)
1209 if self.options.raw:
1210 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1215 @declare_command("slice_hrn rspec","")
1216 def allocate(self, options, args):
1218 allocate resources to the named slice (Allocate)
1220 server = self.sliceapi()
1221 server_version = self.get_cached_server_version(server)
1223 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1226 creds = [self.slice_credential(slice_hrn)]
1228 delegated_cred = None
1229 if server_version.get('interface') == 'slicemgr':
1230 # delegate our cred to the slice manager
1231 # do not delegate cred to slicemgr...not working at the moment
1233 #if server_version.get('hrn'):
1234 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1235 #elif server_version.get('urn'):
1236 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1238 if options.show_credential:
1239 show_credentials(creds)
1242 rspec_file = self.get_rspec_file(args[1])
1243 rspec = open(rspec_file).read()
1245 api_options ['call_id'] = unique_call_id()
1249 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1250 remove_none_fields(slice_records[0])
1251 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1252 slice_record = slice_records[0]
1253 user_hrns = slice_record['reg-researchers']
1254 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1255 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1256 sfa_users = sfa_users_arg(user_records, slice_record)
1257 geni_users = pg_users_arg(user_records)
1259 api_options['sfa_users'] = sfa_users
1260 api_options['geni_users'] = geni_users
1262 result = server.Allocate(slice_urn, creds, rspec, api_options)
1263 value = ReturnValue.get_value(result)
1264 if self.options.raw:
1265 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1266 if options.file is not None:
1267 save_rspec_to_file (value['geni_rspec'], options.file)
1268 if (self.options.raw is None) and (options.file is None):
1273 @declare_command("slice_hrn [<sliver_urn>...]","")
1274 def provision(self, options, args):
1276 provision all or named already allocated slivers of the named slice (Provision)
1278 server = self.sliceapi()
1279 server_version = self.get_cached_server_version(server)
1281 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1283 # we have sliver urns
1284 sliver_urns = args[1:]
1286 # we provision all the slivers of the slice
1287 sliver_urns = [slice_urn]
1290 creds = [self.slice_credential(slice_hrn)]
1291 delegated_cred = None
1292 if server_version.get('interface') == 'slicemgr':
1293 # delegate our cred to the slice manager
1294 # do not delegate cred to slicemgr...not working at the moment
1296 #if server_version.get('hrn'):
1297 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1298 #elif server_version.get('urn'):
1299 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1301 if options.show_credential:
1302 show_credentials(creds)
1305 api_options ['call_id'] = unique_call_id()
1307 # set the requtested rspec version
1308 version_manager = VersionManager()
1309 rspec_version = version_manager._get_version('geni', '3').to_dict()
1310 api_options['geni_rspec_version'] = rspec_version
1313 # need to pass along user keys to the aggregate.
1315 # { urn: urn:publicid:IDN+emulab.net+user+alice
1316 # keys: [<ssh key A>, <ssh key B>]
1319 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1320 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1321 slice_record = slice_records[0]
1322 user_hrns = slice_record['reg-researchers']
1323 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1324 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1325 users = pg_users_arg(user_records)
1327 api_options['geni_users'] = users
1328 result = server.Provision(sliver_urns, creds, api_options)
1329 value = ReturnValue.get_value(result)
1330 if self.options.raw:
1331 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1332 if options.file is not None:
1333 save_rspec_to_file (value['geni_rspec'], options.file)
1334 if (self.options.raw is None) and (options.file is None):
1338 @declare_command("slice_hrn","")
1339 def status(self, options, args):
1341 retrieve the status of the slivers belonging to the named slice (Status)
1343 server = self.sliceapi()
1347 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1350 slice_cred = self.slice_credential(slice_hrn)
1351 creds = [slice_cred]
1353 # options and call_id when supported
1355 api_options['call_id']=unique_call_id()
1356 if options.show_credential:
1357 show_credentials(creds)
1358 result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1359 value = ReturnValue.get_value(result)
1360 if self.options.raw:
1361 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1364 # Thierry: seemed to be missing
1367 @declare_command("slice_hrn [<sliver_urn>...] action","")
1368 def action(self, options, args):
1370 Perform the named operational action on all or named slivers of the named slice
1372 server = self.sliceapi()
1376 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1378 # we have sliver urns
1379 sliver_urns = args[1:-1]
1381 # we provision all the slivers of the slice
1382 sliver_urns = [slice_urn]
1385 slice_cred = self.slice_credential(args[0])
1386 creds = [slice_cred]
1387 if options.delegate:
1388 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1389 creds.append(delegated_cred)
1391 result = server.PerformOperationalAction(sliver_urns, creds, action , api_options)
1392 value = ReturnValue.get_value(result)
1393 if self.options.raw:
1394 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1399 @declare_command("slice_hrn [<sliver_urn>...] time","")
1400 def renew(self, options, args):
1404 server = self.sliceapi()
1409 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1412 # we have sliver urns
1413 sliver_urns = args[1:-1]
1415 # we provision all the slivers of the slice
1416 sliver_urns = [slice_urn]
1417 input_time = args[-1]
1419 # time: don't try to be smart on the time format, server-side will
1421 slice_cred = self.slice_credential(args[0])
1422 creds = [slice_cred]
1423 # options and call_id when supported
1425 api_options['call_id']=unique_call_id()
1426 if options.show_credential:
1427 show_credentials(creds)
1428 result = server.Renew(sliver_urns, creds, input_time, *self.ois(server,api_options))
1429 value = ReturnValue.get_value(result)
1430 if self.options.raw:
1431 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1437 @declare_command("slice_hrn","")
1438 def shutdown(self, options, args):
1440 shutdown named slice (Shutdown)
1442 server = self.sliceapi()
1445 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1447 slice_cred = self.slice_credential(slice_hrn)
1448 creds = [slice_cred]
1449 result = server.Shutdown(slice_urn, creds)
1450 value = ReturnValue.get_value(result)
1451 if self.options.raw:
1452 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1458 @declare_command("[name]","")
1459 def gid(self, options, args):
1461 Create a GID (CreateGid)
1466 target_hrn = args[0]
1467 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1468 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1470 filename = options.file
1472 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1473 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1474 GID(string=gid).save_to_file(filename)
1476 ####################
1477 @declare_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1479 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1480 the set of credentials in the scope for this call would be
1481 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1483 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1485 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1486 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1487 because of the two -s options
1490 def delegate (self, options, args):
1492 (locally) create delegate credential for use by given hrn
1493 make sure to check for 'sfi myslice' instead if you plan
1500 # support for several delegations in the same call
1501 # so first we gather the things to do
1503 for slice_hrn in options.delegate_slices:
1504 message="%s.slice"%slice_hrn
1505 original = self.slice_credential_string(slice_hrn)
1506 tuples.append ( (message, original,) )
1507 if options.delegate_pi:
1508 my_authority=self.authority
1509 message="%s.pi"%my_authority
1510 original = self.my_authority_credential_string()
1511 tuples.append ( (message, original,) )
1512 for auth_hrn in options.delegate_auths:
1513 message="%s.auth"%auth_hrn
1514 original=self.authority_credential_string(auth_hrn)
1515 tuples.append ( (message, original, ) )
1516 # if nothing was specified at all at this point, let's assume -u
1517 if not tuples: options.delegate_user=True
1519 if options.delegate_user:
1520 message="%s.user"%self.user
1521 original = self.my_credential_string
1522 tuples.append ( (message, original, ) )
1524 # default type for beneficial is user unless -A
1525 if options.delegate_to_authority: to_type='authority'
1526 else: to_type='user'
1528 # let's now handle all this
1529 # it's all in the filenaming scheme
1530 for (message,original) in tuples:
1531 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1532 delegated_credential = Credential (string=delegated_string)
1533 filename = os.path.join ( self.options.sfi_dir,
1534 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1535 delegated_credential.save_to_file(filename, save_parents=True)
1536 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1538 ####################
1539 @declare_command("","""$ less +/myslice sfi_config
1541 backend = http://manifold.pl.sophia.inria.fr:7080
1542 # the HRN that myslice uses, so that we are delegating to
1543 delegate = ple.upmc.slicebrowser
1544 # platform - this is a myslice concept
1546 # username - as of this writing (May 2013) a simple login name
1550 will first collect the slices that you are part of, then make sure
1551 all your credentials are up-to-date (read: refresh expired ones)
1552 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1553 and upload them all on myslice backend, using 'platform' and 'user'.
1554 A password will be prompted for the upload part.
1556 $ sfi -v myslice -- or sfi -vv myslice
1557 same but with more and more verbosity
1559 $ sfi m -b http://mymanifold.foo.com:7080/
1560 is synonym to sfi myslice as no other command starts with an 'm'
1561 and uses a custom backend for this one call
1564 def myslice (self, options, args):
1566 """ This helper is for refreshing your credentials at myslice; it will
1567 * compute all the slices that you currently have credentials on
1568 * refresh all your credentials (you as a user and pi, your slices)
1569 * upload them to the manifold backend server
1570 for last phase, sfi_config is read to look for the [myslice] section,
1571 and namely the 'backend', 'delegate' and 'user' settings"""
1577 # enable info by default
1578 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1579 ### the rough sketch goes like this
1580 # (0) produce a p12 file
1581 self.client_bootstrap.my_pkcs12()
1583 # (a) rain check for sufficient config in sfi_config
1585 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1586 for key in myslice_keys:
1588 # oct 2013 - I'm finding myself juggling with config files
1589 # so a couple of command-line options can now override config
1590 if hasattr(options,key) and getattr(options,key) is not None:
1591 value=getattr(options,key)
1593 full_key="MYSLICE_" + key.upper()
1594 value=getattr(self.config_instance,full_key,None)
1595 if value: myslice_dict[key]=value
1596 else: print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1597 if len(myslice_dict) != len(myslice_keys):
1600 # (b) figure whether we are PI for the authority where we belong
1601 self.logger.info("Resolving our own id %s"%self.user)
1602 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1603 if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1604 my_record=my_records[0]
1605 my_auths_all = my_record['reg-pi-authorities']
1606 self.logger.info("Found %d authorities that we are PI for"%len(my_auths_all))
1607 self.logger.debug("They are %s"%(my_auths_all))
1609 my_auths = my_auths_all
1610 if options.delegate_auths:
1611 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1612 self.logger.debug("Restricted to user-provided auths"%(my_auths))
1614 # (c) get the set of slices that we are in
1615 my_slices_all=my_record['reg-slices']
1616 self.logger.info("Found %d slices that we are member of"%len(my_slices_all))
1617 self.logger.debug("They are: %s"%(my_slices_all))
1619 my_slices = my_slices_all
1620 # if user provided slices, deal only with these - if they are found
1621 if options.delegate_slices:
1622 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1623 self.logger.debug("Restricted to user-provided slices: %s"%(my_slices))
1625 # (d) make sure we have *valid* credentials for all these
1627 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1628 for auth_hrn in my_auths:
1629 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1630 for slice_hrn in my_slices:
1631 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1633 # (e) check for the delegated version of these
1634 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1635 # switch to myslice using an authority instead of a user
1636 delegatee_type='user'
1637 delegatee_hrn=myslice_dict['delegate']
1638 hrn_delegated_credentials = []
1639 for (hrn, htype, credential) in hrn_credentials:
1640 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1641 # save these so user can monitor what she's uploaded
1642 filename = os.path.join ( self.options.sfi_dir,
1643 "%s.%s_for_%s.%s.cred"%(hrn,htype,delegatee_hrn,delegatee_type))
1644 with file(filename,'w') as f:
1645 f.write(delegated_credential)
1646 self.logger.debug("(Over)wrote %s"%filename)
1647 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1649 # (f) and finally upload them to manifold server
1650 # xxx todo add an option so the password can be set on the command line
1651 # (but *NOT* in the config file) so other apps can leverage this
1652 self.logger.info("Uploading on backend at %s"%myslice_dict['backend'])
1653 uploader = ManifoldUploader (logger=self.logger,
1654 url=myslice_dict['backend'],
1655 platform=myslice_dict['platform'],
1656 username=myslice_dict['username'],
1657 password=options.password)
1658 uploader.prompt_all()
1659 (count_all,count_success)=(0,0)
1660 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1662 inspect=Credential(string=delegated_credential)
1663 expire_datetime=inspect.get_expiration()
1664 message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
1665 if uploader.upload(delegated_credential,message=message):
1668 self.logger.info("Successfully uploaded %d/%d credentials"%(count_success,count_all))
1670 # at first I thought we would want to save these,
1671 # like 'sfi delegate does' but on second thought
1672 # it is probably not helpful as people would not
1673 # need to run 'sfi delegate' at all anymore
1674 if count_success != count_all: sys.exit(1)
1677 @declare_command("cred","")
1678 def trusted(self, options, args):
1680 return the trusted certs at this interface (get_trusted_certs)
1682 if options.registry_interface:
1683 server=self.registry()
1685 server = self.sliceapi()
1686 cred = self.my_authority_credential_string()
1687 trusted_certs = server.get_trusted_certs(cred)
1688 if not options.registry_interface:
1689 trusted_certs = ReturnValue.get_value(trusted_certs)
1691 for trusted_cert in trusted_certs:
1692 print "\n===========================================================\n"
1693 gid = GID(string=trusted_cert)
1695 cert = Certificate(string=trusted_cert)
1696 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1697 print "Certificate:\n%s\n\n"%trusted_cert