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, 'pis') and options.pis:
212 record_dict['pi'] = options.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)
267 # dirty hack to make this class usable from the outside
268 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
271 def default_sfi_dir ():
272 if os.path.isfile("./sfi_config"):
275 return os.path.expanduser("~/.sfi/")
277 # dummy to meet Sfi's expectations for its 'options' field
278 # i.e. s/t we can do setattr on
282 def __init__ (self,options=None):
283 if options is None: options=Sfi.DummyOptions()
284 for opt in Sfi.required_options:
285 if not hasattr(options,opt): setattr(options,opt,None)
286 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
287 self.options = options
289 self.authority = None
290 self.logger = sfi_logger
291 self.logger.enable_console()
292 ### various auxiliary material that we keep at hand
294 # need to call this other than just 'config' as we have a command/method with that name
295 self.config_instance=None
296 self.config_file=None
297 self.client_bootstrap=None
299 ### suitable if no reasonable command has been provided
300 def print_commands_help (self, options):
301 verbose=getattr(options,'verbose')
302 format3="%10s %-35s %s"
306 print format3%("command","cmd_args","description")
310 self.create_parser_global().print_help()
311 # preserve order from the code
312 for command in commands_list:
314 (doc, args_string, example, canonical) = commands_dict[command]
316 print "Cannot find info on command %s - skipped"%command
320 if command==canonical:
321 doc=doc.replace("\n","\n"+format3offset*' ')
322 print format3%(command,args_string,doc)
324 self.create_parser_command(command).print_help()
326 print format3%(command,"<<alias for %s>>"%canonical,"")
328 ### now if a known command was found we can be more verbose on that one
329 def print_help (self):
330 print "==================== Generic sfi usage"
331 self.sfi_parser.print_help()
332 (doc,_,example,canonical)=commands_dict[self.command]
333 if canonical != self.command:
334 print "\n==================== NOTE: %s is an alias for genuine %s"%(self.command,canonical)
335 self.command=canonical
336 print "\n==================== Purpose of %s"%self.command
338 print "\n==================== Specific usage for %s"%self.command
339 self.command_parser.print_help()
341 print "\n==================== %s example(s)"%self.command
344 def create_parser_global(self):
345 # Generate command line parser
346 parser = OptionParser(add_help_option=False,
347 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
348 description="Commands: %s"%(" ".join(commands_list)))
349 parser.add_option("-r", "--registry", dest="registry",
350 help="root registry", metavar="URL", default=None)
351 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
352 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
353 parser.add_option("-R", "--raw", dest="raw", default=None,
354 help="Save raw, unparsed server response to a file")
355 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
356 help="raw file format ([text]|pickled|json)", default="text",
357 choices=("text","pickled","json"))
358 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
359 help="text string to write before and after raw output")
360 parser.add_option("-d", "--dir", dest="sfi_dir",
361 help="config & working directory - default is %default",
362 metavar="PATH", default=Sfi.default_sfi_dir())
363 parser.add_option("-u", "--user", dest="user",
364 help="user name", metavar="HRN", default=None)
365 parser.add_option("-a", "--auth", dest="auth",
366 help="authority name", metavar="HRN", default=None)
367 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
368 help="verbose mode - cumulative")
369 parser.add_option("-D", "--debug",
370 action="store_true", dest="debug", default=False,
371 help="Debug (xml-rpc) protocol messages")
372 # would it make sense to use ~/.ssh/id_rsa as a default here ?
373 parser.add_option("-k", "--private-key",
374 action="store", dest="user_private_key", default=None,
375 help="point to the private key file to use if not yet installed in sfi_dir")
376 parser.add_option("-t", "--timeout", dest="timeout", default=None,
377 help="Amout of time to wait before timing out the request")
378 parser.add_option("-h", "--help",
379 action="store_true", dest="help", default=False,
380 help="one page summary on commands & exit")
381 parser.disable_interspersed_args()
386 def create_parser_command(self, command):
387 if command not in commands_dict:
388 msg="Invalid command\n"
390 msg += ','.join(commands_list)
391 self.logger.critical(msg)
394 # retrieve args_string
395 (_, args_string, __,canonical) = commands_dict[command]
397 parser = OptionParser(add_help_option=False,
398 usage="sfi [sfi_options] %s [cmd_options] %s"
399 % (command, args_string))
400 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
401 help="Summary of one command usage")
403 if canonical in ("config"):
404 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
405 help='how myslice config variables as well')
407 if canonical in ("version"):
408 parser.add_option("-l","--local",
409 action="store_true", dest="version_local", default=False,
410 help="display version of the local client")
412 if canonical in ("version", "trusted"):
413 parser.add_option("-R","--registry_interface",
414 action="store_true", dest="registry_interface", default=False,
415 help="target the registry interface instead of slice interface")
417 if canonical in ("register", "update"):
418 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
419 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
420 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
421 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
423 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
424 default='', type="str", action='callback', callback=optparse_listvalue_callback)
425 parser.add_option('-r', '--researchers', dest='reg_researchers', metavar='<researchers>',
426 help='Set/replace slice researchers - use -r none to reset', default=None, type="str", action='callback',
427 callback=optparse_listvalue_callback)
428 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
429 default='', type="str", action='callback', callback=optparse_listvalue_callback)
430 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
431 action="callback", callback=optparse_dictvalue_callback, nargs=1,
432 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
434 # user specifies remote aggregate/sm/component
435 if canonical in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
436 "action", "shutdown", "renew", "status"):
437 parser.add_option("-d", "--delegate", dest="delegate", default=None,
439 help="Include a credential delegated to the user's root"+\
440 "authority in set of credentials for this call")
442 # show_credential option
443 if canonical in ("list","resources", "describe", "provision", "allocate", "register","update","remove","delete","status","renew"):
444 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
445 help="show credential(s) used in human-readable form")
446 # registy filter option
447 if canonical in ("list", "show", "remove"):
448 parser.add_option("-t", "--type", dest="type", type="choice",
449 help="type filter ([all]|user|slice|authority|node|aggregate)",
450 choices=("all", "user", "slice", "authority", "node", "aggregate"),
452 if canonical in ("show"):
453 parser.add_option("-k","--key",dest="keys",action="append",default=[],
454 help="specify specific keys to be displayed from record")
455 parser.add_option("-n","--no-details",dest="no_details",action="store_true",default=False,
456 help="call Resolve without the 'details' option")
457 if canonical in ("resources", "describe"):
459 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="GENI 3",
460 help="schema type and version of resulting RSpec")
461 # disable/enable cached rspecs
462 parser.add_option("-c", "--current", dest="current", default=False,
464 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
466 parser.add_option("-f", "--format", dest="format", type="choice",
467 help="display format ([xml]|dns|ip)", default="xml",
468 choices=("xml", "dns", "ip"))
469 #panos: a new option to define the type of information about resources a user is interested in
470 parser.add_option("-i", "--info", dest="info",
471 help="optional component information", default=None)
472 # a new option to retreive or not reservation-oriented RSpecs (leases)
473 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
474 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
475 choices=("all", "resources", "leases"), default="resources")
478 if canonical in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
479 parser.add_option("-o", "--output", dest="file",
480 help="output XML to file", metavar="FILE", default=None)
482 if canonical in ("show", "list"):
483 parser.add_option("-f", "--format", dest="format", type="choice",
484 help="display format ([text]|xml)", default="text",
485 choices=("text", "xml"))
487 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
488 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
489 choices=("xml", "xmllist", "hrnlist"))
490 if canonical == 'list':
491 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
492 help="list all child records", default=False)
493 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
494 help="gives details, like user keys", default=False)
495 if canonical in ("delegate"):
496 parser.add_option("-u", "--user",
497 action="store_true", dest="delegate_user", default=False,
498 help="delegate your own credentials; default if no other option is provided")
499 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
500 metavar="slice_hrn", help="delegate cred. for slice HRN")
501 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
502 metavar='auth_hrn', help="delegate cred for auth HRN")
503 # this primarily is a shorthand for -a my_hrn
504 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
505 help="delegate your PI credentials, so s.t. like -a your_hrn^")
506 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
507 help="""by default the mandatory argument is expected to be a user,
508 use this if you mean an authority instead""")
510 if canonical in ("myslice"):
511 parser.add_option("-p","--password",dest='password',action='store',default=None,
512 help="specify mainfold password on the command line")
513 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
514 metavar="slice_hrn", help="delegate cred. for slice HRN")
515 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
516 metavar='auth_hrn', help="delegate PI cred for auth HRN")
517 parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
518 parser.add_option('-b', '--backend', dest='backend', help="Override 'backend' from the config file")
524 # Main: parse arguments and dispatch to command
526 def dispatch(self, command, command_options, command_args):
527 (doc, args_string, example, canonical) = commands_dict[command]
528 method=getattr(self, canonical, None)
530 print "sfi: unknown command %s"%command
531 raise SystemExit,"Unknown command %s"%command
532 return method(command_options, command_args)
535 self.sfi_parser = self.create_parser_global()
536 (options, args) = self.sfi_parser.parse_args()
538 self.print_commands_help(options)
540 self.options = options
542 self.logger.setLevelFromOptVerbose(self.options.verbose)
545 self.logger.critical("No command given. Use -h for help.")
546 self.print_commands_help(options)
549 # complete / find unique match with command set
550 command_candidates = Candidates (commands_list)
552 command = command_candidates.only_match(input)
554 self.print_commands_help(options)
556 # second pass options parsing
558 self.command_parser = self.create_parser_command(command)
559 (command_options, command_args) = self.command_parser.parse_args(args[1:])
560 if command_options.help:
563 self.command_options = command_options
567 self.logger.debug("Command=%s" % self.command)
570 self.dispatch(command, command_options, command_args)
574 self.logger.log_exc ("sfi command %s failed"%command)
580 def read_config(self):
581 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
582 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
584 if Config.is_ini(config_file):
585 config = Config (config_file)
587 # try upgrading from shell config format
588 fp, fn = mkstemp(suffix='sfi_config', text=True)
590 # we need to preload the sections we want parsed
591 # from the shell config
592 config.add_section('sfi')
593 # sface users should be able to use this same file to configure their stuff
594 config.add_section('sface')
595 # manifold users should be able to specify the details
596 # of their backend server here for 'sfi myslice'
597 config.add_section('myslice')
598 config.load(config_file)
600 shutil.move(config_file, shell_config_file)
602 config.save(config_file)
605 self.logger.critical("Failed to read configuration file %s"%config_file)
606 self.logger.info("Make sure to remove the export clauses and to add quotes")
607 if self.options.verbose==0:
608 self.logger.info("Re-run with -v for more details")
610 self.logger.log_exc("Could not read config file %s"%config_file)
613 self.config_instance=config
616 if (self.options.sm is not None):
617 self.sm_url = self.options.sm
618 elif hasattr(config, "SFI_SM"):
619 self.sm_url = config.SFI_SM
621 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
625 if (self.options.registry is not None):
626 self.reg_url = self.options.registry
627 elif hasattr(config, "SFI_REGISTRY"):
628 self.reg_url = config.SFI_REGISTRY
630 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
634 if (self.options.user is not None):
635 self.user = self.options.user
636 elif hasattr(config, "SFI_USER"):
637 self.user = config.SFI_USER
639 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
643 if (self.options.auth is not None):
644 self.authority = self.options.auth
645 elif hasattr(config, "SFI_AUTH"):
646 self.authority = config.SFI_AUTH
648 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
651 self.config_file=config_file
656 # Get various credential and spec files
658 # Establishes limiting conventions
659 # - conflates MAs and SAs
660 # - assumes last token in slice name is unique
662 # Bootstraps credentials
663 # - bootstrap user credential from self-signed certificate
664 # - bootstrap authority credential from user credential
665 # - bootstrap slice credential from user credential
668 # init self-signed cert, user credentials and gid
669 def bootstrap (self):
670 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
672 # if -k is provided, use this to initialize private key
673 if self.options.user_private_key:
674 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
676 # trigger legacy compat code if needed
677 # the name has changed from just <leaf>.pkey to <hrn>.pkey
678 if not os.path.isfile(client_bootstrap.private_key_filename()):
679 self.logger.info ("private key not found, trying legacy name")
681 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
682 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
683 client_bootstrap.init_private_key_if_missing (legacy_private_key)
684 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
686 self.logger.log_exc("Can't find private key ")
690 client_bootstrap.bootstrap_my_gid()
691 # extract what's needed
692 self.private_key = client_bootstrap.private_key()
693 self.my_credential_string = client_bootstrap.my_credential_string ()
694 self.my_credential = {'geni_type': 'geni_sfa',
696 'geni_value': self.my_credential_string}
697 self.my_gid = client_bootstrap.my_gid ()
698 self.client_bootstrap = client_bootstrap
701 def my_authority_credential_string(self):
702 if not self.authority:
703 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
705 return self.client_bootstrap.authority_credential_string (self.authority)
707 def authority_credential_string(self, auth_hrn):
708 return self.client_bootstrap.authority_credential_string (auth_hrn)
710 def slice_credential_string(self, name):
711 return self.client_bootstrap.slice_credential_string (name)
713 def slice_credential(self, name):
714 return {'geni_type': 'geni_sfa',
716 'geni_value': self.slice_credential_string(name)}
718 # xxx should be supported by sfaclientbootstrap as well
719 def delegate_cred(self, object_cred, hrn, type='authority'):
720 # the gid and hrn of the object we are delegating
721 if isinstance(object_cred, str):
722 object_cred = Credential(string=object_cred)
723 object_gid = object_cred.get_gid_object()
724 object_hrn = object_gid.get_hrn()
726 if not object_cred.get_privileges().get_all_delegate():
727 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
730 # the delegating user's gid
731 caller_gidfile = self.my_gid()
733 # the gid of the user who will be delegated to
734 delegee_gid = self.client_bootstrap.gid(hrn,type)
735 delegee_hrn = delegee_gid.get_hrn()
736 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
737 return dcred.save_to_string(save_parents=True)
740 # Management of the servers
745 if not hasattr (self, 'registry_proxy'):
746 self.logger.info("Contacting Registry at: %s"%self.reg_url)
747 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
748 timeout=self.options.timeout, verbose=self.options.debug)
749 return self.registry_proxy
753 if not hasattr (self, 'sliceapi_proxy'):
754 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
755 if hasattr(self.command_options,'component') and self.command_options.component:
756 # resolve the hrn at the registry
757 node_hrn = self.command_options.component
758 records = self.registry().Resolve(node_hrn, self.my_credential_string)
759 records = filter_records('node', records)
761 self.logger.warning("No such component:%r"% opts.component)
763 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
764 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
766 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
767 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
768 self.sm_url = 'http://' + self.sm_url
769 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
770 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
771 timeout=self.options.timeout, verbose=self.options.debug)
772 return self.sliceapi_proxy
774 def get_cached_server_version(self, server):
775 # check local cache first
778 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
779 cache_key = server.url + "-version"
781 cache = Cache(cache_file)
784 self.logger.info("Local cache not found at: %s" % cache_file)
787 version = cache.get(cache_key)
790 result = server.GetVersion()
791 version= ReturnValue.get_value(result)
792 # cache version for 20 minutes
793 cache.add(cache_key, version, ttl= 60*20)
794 self.logger.info("Updating cache file %s" % cache_file)
795 cache.save_to_file(cache_file)
799 ### resurrect this temporarily so we can support V1 aggregates for a while
800 def server_supports_options_arg(self, server):
802 Returns true if server support the optional call_id arg, false otherwise.
804 server_version = self.get_cached_server_version(server)
806 # xxx need to rewrite this
807 if int(server_version.get('geni_api')) >= 2:
811 def server_supports_call_id_arg(self, server):
812 server_version = self.get_cached_server_version(server)
814 if 'sfa' in server_version and 'code_tag' in server_version:
815 code_tag = server_version['code_tag']
816 code_tag_parts = code_tag.split("-")
817 version_parts = code_tag_parts[0].split(".")
818 major, minor = version_parts[0], version_parts[1]
819 rev = code_tag_parts[1]
820 if int(major) == 1 and minor == 0 and build >= 22:
824 ### ois = options if supported
825 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
826 def ois (self, server, option_dict):
827 if self.server_supports_options_arg (server):
829 elif self.server_supports_call_id_arg (server):
830 return [ unique_call_id () ]
834 ### cis = call_id if supported - like ois
835 def cis (self, server):
836 if self.server_supports_call_id_arg (server):
837 return [ unique_call_id ]
841 ######################################## miscell utilities
842 def get_rspec_file(self, rspec):
843 if (os.path.isabs(rspec)):
846 file = os.path.join(self.options.sfi_dir, rspec)
847 if (os.path.isfile(file)):
850 self.logger.critical("No such rspec file %s"%rspec)
853 def get_record_file(self, record):
854 if (os.path.isabs(record)):
857 file = os.path.join(self.options.sfi_dir, record)
858 if (os.path.isfile(file)):
861 self.logger.critical("No such registry record file %s"%record)
865 #==========================================================================
866 # Following functions implement the commands
868 # Registry-related commands
869 #==========================================================================
871 @declare_command("","")
872 def config (self, options, args):
873 "Display contents of current config"
874 print "# From configuration file %s"%self.config_file
875 flags=[ ('sfi', [ ('registry','reg_url'),
876 ('auth','authority'),
882 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
884 for (section, tuples) in flags:
887 for (external_name, internal_name) in tuples:
888 print "%-20s = %s"%(external_name,getattr(self,internal_name))
891 varname="%s_%s"%(section.upper(),name.upper())
892 value=getattr(self.config_instance,varname)
893 print "%-20s = %s"%(name,value)
895 @declare_command("","")
896 def version(self, options, args):
898 display an SFA server version (GetVersion)
899 or version information about sfi itself
901 if options.version_local:
902 version=version_core()
904 if options.registry_interface:
905 server=self.registry()
907 server = self.sliceapi()
908 result = server.GetVersion()
909 version = ReturnValue.get_value(result)
911 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
913 pprinter = PrettyPrinter(indent=4)
914 pprinter.pprint(version)
916 @declare_command("authority","")
917 def list(self, options, args):
919 list entries in named authority registry (List)
926 if options.recursive:
927 opts['recursive'] = options.recursive
929 if options.show_credential:
930 show_credentials(self.my_credential_string)
932 list = self.registry().List(hrn, self.my_credential_string, options)
934 raise Exception, "Not enough parameters for the 'list' command"
936 # filter on person, slice, site, node, etc.
937 # This really should be in the self.filter_records funct def comment...
938 list = filter_records(options.type, list)
939 terminal_render (list, options)
941 save_records_to_file(options.file, list, options.fileformat)
944 @declare_command("name","")
945 def show(self, options, args):
947 show details about named registry record (Resolve)
953 # explicitly require Resolve to run in details mode
955 if not options.no_details: resolve_options['details']=True
956 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, resolve_options)
957 record_dicts = filter_records(options.type, record_dicts)
959 self.logger.error("No record of type %s"% options.type)
961 # user has required to focus on some keys
963 def project (record):
965 for key in options.keys:
966 try: projected[key]=record[key]
969 record_dicts = [ project (record) for record in record_dicts ]
970 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
971 for record in records:
972 if (options.format == "text"): record.dump(sort=True)
973 else: print record.save_as_xml()
975 save_records_to_file(options.file, record_dicts, options.fileformat)
978 # this historically was named 'add', it is now 'register' with an alias for legacy
979 @declare_command("[xml-filename]","",['add'])
980 def register(self, options, args):
981 """create new record in registry (Register)
982 from command line options (recommended)
983 old-school method involving an xml file still supported"""
985 auth_cred = self.my_authority_credential_string()
986 if options.show_credential:
987 show_credentials(auth_cred)
994 record_filepath = args[0]
995 rec_file = self.get_record_file(record_filepath)
996 record_dict.update(load_record_from_file(rec_file).todict())
998 print "Cannot load record file %s"%record_filepath
1001 record_dict.update(load_record_from_opts(options).todict())
1002 # we should have a type by now
1003 if 'type' not in record_dict :
1006 # this is still planetlab dependent.. as plc will whine without that
1007 # also, it's only for adding
1008 if record_dict['type'] == 'user':
1009 if not 'first_name' in record_dict:
1010 record_dict['first_name'] = record_dict['hrn']
1011 if 'last_name' not in record_dict:
1012 record_dict['last_name'] = record_dict['hrn']
1013 return self.registry().Register(record_dict, auth_cred)
1015 @declare_command("[xml-filename]","")
1016 def update(self, options, args):
1017 """update record into registry (Update)
1018 from command line options (recommended)
1019 old-school method involving an xml file still supported"""
1022 record_filepath = args[0]
1023 rec_file = self.get_record_file(record_filepath)
1024 record_dict.update(load_record_from_file(rec_file).todict())
1026 record_dict.update(load_record_from_opts(options).todict())
1027 # at the very least we need 'type' here
1028 if 'type' not in record_dict:
1032 # don't translate into an object, as this would possibly distort
1033 # user-provided data; e.g. add an 'email' field to Users
1034 if record_dict['type'] == "user":
1035 if record_dict['hrn'] == self.user:
1036 cred = self.my_credential_string
1038 cred = self.my_authority_credential_string()
1039 elif record_dict['type'] in ["slice"]:
1041 cred = self.slice_credential_string(record_dict['hrn'])
1042 except ServerException, e:
1043 # XXX smbaker -- once we have better error return codes, update this
1044 # to do something better than a string compare
1045 if "Permission error" in e.args[0]:
1046 cred = self.my_authority_credential_string()
1049 elif record_dict['type'] in ["authority"]:
1050 cred = self.my_authority_credential_string()
1051 elif record_dict['type'] == 'node':
1052 cred = self.my_authority_credential_string()
1054 raise "unknown record type" + record_dict['type']
1055 if options.show_credential:
1056 show_credentials(cred)
1057 return self.registry().Update(record_dict, cred)
1059 @declare_command("hrn","")
1060 def remove(self, options, args):
1061 "remove registry record by name (Remove)"
1062 auth_cred = self.my_authority_credential_string()
1070 if options.show_credential:
1071 show_credentials(auth_cred)
1072 return self.registry().Remove(hrn, auth_cred, type)
1074 # ==================================================================
1075 # Slice-related commands
1076 # ==================================================================
1078 # show rspec for named slice
1079 @declare_command("","")
1080 def resources(self, options, args):
1082 discover available resources (ListResources)
1084 server = self.sliceapi()
1087 creds = [self.my_credential]
1088 if options.delegate:
1089 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1090 if options.show_credential:
1091 show_credentials(creds)
1093 # no need to check if server accepts the options argument since the options has
1094 # been a required argument since v1 API
1096 # always send call_id to v2 servers
1097 api_options ['call_id'] = unique_call_id()
1098 # ask for cached value if available
1099 api_options ['cached'] = True
1101 api_options['info'] = options.info
1102 if options.list_leases:
1103 api_options['list_leases'] = options.list_leases
1105 if options.current == True:
1106 api_options['cached'] = False
1108 api_options['cached'] = True
1109 if options.rspec_version:
1110 version_manager = VersionManager()
1111 server_version = self.get_cached_server_version(server)
1112 if 'sfa' in server_version:
1113 # just request the version the client wants
1114 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1116 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1118 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1119 result = server.ListResources (creds, api_options)
1120 value = ReturnValue.get_value(result)
1121 if self.options.raw:
1122 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1123 if options.file is not None:
1124 save_rspec_to_file(value, options.file)
1125 if (self.options.raw is None) and (options.file is None):
1126 display_rspec(value, options.format)
1130 @declare_command("slice_hrn","")
1131 def describe(self, options, args):
1133 shows currently allocated/provisioned resources
1134 of the named slice or set of slivers (Describe)
1136 server = self.sliceapi()
1139 creds = [self.slice_credential(args[0])]
1140 if options.delegate:
1141 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1142 if options.show_credential:
1143 show_credentials(creds)
1145 api_options = {'call_id': unique_call_id(),
1147 #'info': options.info,
1148 'list_leases': options.list_leases,
1149 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1152 api_options['info'] = options.info
1154 if options.rspec_version:
1155 version_manager = VersionManager()
1156 server_version = self.get_cached_server_version(server)
1157 if 'sfa' in server_version:
1158 # just request the version the client wants
1159 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1161 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1162 urn = Xrn(args[0], type='slice').get_urn()
1163 result = server.Describe([urn], creds, api_options)
1164 value = ReturnValue.get_value(result)
1165 if self.options.raw:
1166 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1167 if options.file is not None:
1168 save_rspec_to_file(value['geni_rspec'], options.file)
1169 if (self.options.raw is None) and (options.file is None):
1170 display_rspec(value, options.format)
1174 @declare_command("slice_hrn [<sliver_urn>...]","")
1175 def delete(self, options, args):
1177 de-allocate and de-provision all or named slivers of the named slice (Delete)
1179 server = self.sliceapi()
1183 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1186 # we have sliver urns
1187 sliver_urns = args[1:]
1189 # we provision all the slivers of the slice
1190 sliver_urns = [slice_urn]
1193 slice_cred = self.slice_credential(slice_hrn)
1194 creds = [slice_cred]
1196 # options and call_id when supported
1198 api_options ['call_id'] = unique_call_id()
1199 if options.show_credential:
1200 show_credentials(creds)
1201 result = server.Delete(sliver_urns, creds, *self.ois(server, api_options ) )
1202 value = ReturnValue.get_value(result)
1203 if self.options.raw:
1204 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1209 @declare_command("slice_hrn rspec","")
1210 def allocate(self, options, args):
1212 allocate resources to the named slice (Allocate)
1214 server = self.sliceapi()
1215 server_version = self.get_cached_server_version(server)
1217 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1220 creds = [self.slice_credential(slice_hrn)]
1222 delegated_cred = None
1223 if server_version.get('interface') == 'slicemgr':
1224 # delegate our cred to the slice manager
1225 # do not delegate cred to slicemgr...not working at the moment
1227 #if server_version.get('hrn'):
1228 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1229 #elif server_version.get('urn'):
1230 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1232 if options.show_credential:
1233 show_credentials(creds)
1236 rspec_file = self.get_rspec_file(args[1])
1237 rspec = open(rspec_file).read()
1239 api_options ['call_id'] = unique_call_id()
1243 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1244 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1245 slice_record = slice_records[0]
1246 user_hrns = slice_record['reg-researchers']
1247 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1248 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1249 sfa_users = sfa_users_arg(user_records, slice_record)
1250 geni_users = pg_users_arg(user_records)
1252 api_options['sfa_users'] = sfa_users
1253 api_options['geni_users'] = geni_users
1255 result = server.Allocate(slice_urn, creds, rspec, api_options)
1256 value = ReturnValue.get_value(result)
1257 if self.options.raw:
1258 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1259 if options.file is not None:
1260 save_rspec_to_file (value['geni_rspec'], options.file)
1261 if (self.options.raw is None) and (options.file is None):
1266 @declare_command("slice_hrn [<sliver_urn>...]","")
1267 def provision(self, options, args):
1269 provision all or named already allocated slivers of the named slice (Provision)
1271 server = self.sliceapi()
1272 server_version = self.get_cached_server_version(server)
1274 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1276 # we have sliver urns
1277 sliver_urns = args[1:]
1279 # we provision all the slivers of the slice
1280 sliver_urns = [slice_urn]
1283 creds = [self.slice_credential(slice_hrn)]
1284 delegated_cred = None
1285 if server_version.get('interface') == 'slicemgr':
1286 # delegate our cred to the slice manager
1287 # do not delegate cred to slicemgr...not working at the moment
1289 #if server_version.get('hrn'):
1290 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1291 #elif server_version.get('urn'):
1292 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1294 if options.show_credential:
1295 show_credentials(creds)
1298 api_options ['call_id'] = unique_call_id()
1300 # set the requtested rspec version
1301 version_manager = VersionManager()
1302 rspec_version = version_manager._get_version('geni', '3').to_dict()
1303 api_options['geni_rspec_version'] = rspec_version
1306 # need to pass along user keys to the aggregate.
1308 # { urn: urn:publicid:IDN+emulab.net+user+alice
1309 # keys: [<ssh key A>, <ssh key B>]
1312 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1313 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1314 slice_record = slice_records[0]
1315 user_hrns = slice_record['reg-researchers']
1316 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1317 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1318 users = pg_users_arg(user_records)
1320 api_options['geni_users'] = users
1321 result = server.Provision(sliver_urns, creds, api_options)
1322 value = ReturnValue.get_value(result)
1323 if self.options.raw:
1324 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1325 if options.file is not None:
1326 save_rspec_to_file (value['geni_rspec'], options.file)
1327 if (self.options.raw is None) and (options.file is None):
1331 @declare_command("slice_hrn","")
1332 def status(self, options, args):
1334 retrieve the status of the slivers belonging to the named slice (Status)
1336 server = self.sliceapi()
1340 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1343 slice_cred = self.slice_credential(slice_hrn)
1344 creds = [slice_cred]
1346 # options and call_id when supported
1348 api_options['call_id']=unique_call_id()
1349 if options.show_credential:
1350 show_credentials(creds)
1351 result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1352 value = ReturnValue.get_value(result)
1353 if self.options.raw:
1354 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1357 # Thierry: seemed to be missing
1360 @declare_command("slice_hrn [<sliver_urn>...] action","")
1361 def action(self, options, args):
1363 Perform the named operational action on all or named slivers of the named slice
1365 server = self.sliceapi()
1369 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1371 # we have sliver urns
1372 sliver_urns = args[1:-1]
1374 # we provision all the slivers of the slice
1375 sliver_urns = [slice_urn]
1378 slice_cred = self.slice_credential(args[0])
1379 creds = [slice_cred]
1380 if options.delegate:
1381 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1382 creds.append(delegated_cred)
1384 result = server.PerformOperationalAction(sliver_urns, creds, action , api_options)
1385 value = ReturnValue.get_value(result)
1386 if self.options.raw:
1387 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1392 @declare_command("slice_hrn [<sliver_urn>...] time","")
1393 def renew(self, options, args):
1397 server = self.sliceapi()
1402 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1405 # we have sliver urns
1406 sliver_urns = args[1:-1]
1408 # we provision all the slivers of the slice
1409 sliver_urns = [slice_urn]
1410 input_time = args[-1]
1412 # time: don't try to be smart on the time format, server-side will
1414 slice_cred = self.slice_credential(args[0])
1415 creds = [slice_cred]
1416 # options and call_id when supported
1418 api_options['call_id']=unique_call_id()
1419 if options.show_credential:
1420 show_credentials(creds)
1421 result = server.Renew(sliver_urns, creds, input_time, *self.ois(server,api_options))
1422 value = ReturnValue.get_value(result)
1423 if self.options.raw:
1424 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1430 @declare_command("slice_hrn","")
1431 def shutdown(self, options, args):
1433 shutdown named slice (Shutdown)
1435 server = self.sliceapi()
1438 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1440 slice_cred = self.slice_credential(slice_hrn)
1441 creds = [slice_cred]
1442 result = server.Shutdown(slice_urn, creds)
1443 value = ReturnValue.get_value(result)
1444 if self.options.raw:
1445 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1451 @declare_command("[name]","")
1452 def gid(self, options, args):
1454 Create a GID (CreateGid)
1459 target_hrn = args[0]
1460 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1461 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1463 filename = options.file
1465 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1466 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1467 GID(string=gid).save_to_file(filename)
1469 ####################
1470 @declare_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1472 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1473 the set of credentials in the scope for this call would be
1474 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1476 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1478 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1479 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1480 because of the two -s options
1483 def delegate (self, options, args):
1485 (locally) create delegate credential for use by given hrn
1486 make sure to check for 'sfi myslice' instead if you plan
1493 # support for several delegations in the same call
1494 # so first we gather the things to do
1496 for slice_hrn in options.delegate_slices:
1497 message="%s.slice"%slice_hrn
1498 original = self.slice_credential_string(slice_hrn)
1499 tuples.append ( (message, original,) )
1500 if options.delegate_pi:
1501 my_authority=self.authority
1502 message="%s.pi"%my_authority
1503 original = self.my_authority_credential_string()
1504 tuples.append ( (message, original,) )
1505 for auth_hrn in options.delegate_auths:
1506 message="%s.auth"%auth_hrn
1507 original=self.authority_credential_string(auth_hrn)
1508 tuples.append ( (message, original, ) )
1509 # if nothing was specified at all at this point, let's assume -u
1510 if not tuples: options.delegate_user=True
1512 if options.delegate_user:
1513 message="%s.user"%self.user
1514 original = self.my_credential_string
1515 tuples.append ( (message, original, ) )
1517 # default type for beneficial is user unless -A
1518 if options.delegate_to_authority: to_type='authority'
1519 else: to_type='user'
1521 # let's now handle all this
1522 # it's all in the filenaming scheme
1523 for (message,original) in tuples:
1524 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1525 delegated_credential = Credential (string=delegated_string)
1526 filename = os.path.join ( self.options.sfi_dir,
1527 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1528 delegated_credential.save_to_file(filename, save_parents=True)
1529 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1531 ####################
1532 @declare_command("","""$ less +/myslice sfi_config
1534 backend = http://manifold.pl.sophia.inria.fr:7080
1535 # the HRN that myslice uses, so that we are delegating to
1536 delegate = ple.upmc.slicebrowser
1537 # platform - this is a myslice concept
1539 # username - as of this writing (May 2013) a simple login name
1543 will first collect the slices that you are part of, then make sure
1544 all your credentials are up-to-date (read: refresh expired ones)
1545 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1546 and upload them all on myslice backend, using 'platform' and 'user'.
1547 A password will be prompted for the upload part.
1549 $ sfi -v myslice -- or sfi -vv myslice
1550 same but with more and more verbosity
1552 $ sfi m -b http://mymanifold.foo.com:7080/
1553 is synonym to sfi myslice as no other command starts with an 'm'
1554 and uses a custom backend for this one call
1557 def myslice (self, options, args):
1559 """ This helper is for refreshing your credentials at myslice; it will
1560 * compute all the slices that you currently have credentials on
1561 * refresh all your credentials (you as a user and pi, your slices)
1562 * upload them to the manifold backend server
1563 for last phase, sfi_config is read to look for the [myslice] section,
1564 and namely the 'backend', 'delegate' and 'user' settings"""
1570 # enable info by default
1571 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1572 ### the rough sketch goes like this
1573 # (0) produce a p12 file
1574 self.client_bootstrap.my_pkcs12()
1576 # (a) rain check for sufficient config in sfi_config
1578 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1579 for key in myslice_keys:
1581 # oct 2013 - I'm finding myself juggling with config files
1582 # so a couple of command-line options can now override config
1583 if hasattr(options,key) and getattr(options,key) is not None:
1584 value=getattr(options,key)
1586 full_key="MYSLICE_" + key.upper()
1587 value=getattr(self.config_instance,full_key,None)
1588 if value: myslice_dict[key]=value
1589 else: print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1590 if len(myslice_dict) != len(myslice_keys):
1593 # (b) figure whether we are PI for the authority where we belong
1594 self.logger.info("Resolving our own id %s"%self.user)
1595 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1596 if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1597 my_record=my_records[0]
1598 my_auths_all = my_record['reg-pi-authorities']
1599 self.logger.info("Found %d authorities that we are PI for"%len(my_auths_all))
1600 self.logger.debug("They are %s"%(my_auths_all))
1602 my_auths = my_auths_all
1603 if options.delegate_auths:
1604 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1605 self.logger.debug("Restricted to user-provided auths"%(my_auths))
1607 # (c) get the set of slices that we are in
1608 my_slices_all=my_record['reg-slices']
1609 self.logger.info("Found %d slices that we are member of"%len(my_slices_all))
1610 self.logger.debug("They are: %s"%(my_slices_all))
1612 my_slices = my_slices_all
1613 # if user provided slices, deal only with these - if they are found
1614 if options.delegate_slices:
1615 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1616 self.logger.debug("Restricted to user-provided slices: %s"%(my_slices))
1618 # (d) make sure we have *valid* credentials for all these
1620 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1621 for auth_hrn in my_auths:
1622 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1623 for slice_hrn in my_slices:
1624 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1626 # (e) check for the delegated version of these
1627 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1628 # switch to myslice using an authority instead of a user
1629 delegatee_type='user'
1630 delegatee_hrn=myslice_dict['delegate']
1631 hrn_delegated_credentials = []
1632 for (hrn, htype, credential) in hrn_credentials:
1633 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1634 # save these so user can monitor what she's uploaded
1635 filename = os.path.join ( self.options.sfi_dir,
1636 "%s.%s_for_%s.%s.cred"%(hrn,htype,delegatee_hrn,delegatee_type))
1637 with file(filename,'w') as f:
1638 f.write(delegated_credential)
1639 self.logger.debug("(Over)wrote %s"%filename)
1640 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1642 # (f) and finally upload them to manifold server
1643 # xxx todo add an option so the password can be set on the command line
1644 # (but *NOT* in the config file) so other apps can leverage this
1645 self.logger.info("Uploading on backend at %s"%myslice_dict['backend'])
1646 uploader = ManifoldUploader (logger=self.logger,
1647 url=myslice_dict['backend'],
1648 platform=myslice_dict['platform'],
1649 username=myslice_dict['username'],
1650 password=options.password)
1651 uploader.prompt_all()
1652 (count_all,count_success)=(0,0)
1653 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1655 inspect=Credential(string=delegated_credential)
1656 expire_datetime=inspect.get_expiration()
1657 message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
1658 if uploader.upload(delegated_credential,message=message):
1661 self.logger.info("Successfully uploaded %d/%d credentials"%(count_success,count_all))
1663 # at first I thought we would want to save these,
1664 # like 'sfi delegate does' but on second thought
1665 # it is probably not helpful as people would not
1666 # need to run 'sfi delegate' at all anymore
1667 if count_success != count_all: sys.exit(1)
1670 @declare_command("cred","")
1671 def trusted(self, options, args):
1673 return the trusted certs at this interface (get_trusted_certs)
1675 if options.registry_interface:
1676 server=self.registry()
1678 server = self.sliceapi()
1679 cred = self.my_authority_credential_string()
1680 trusted_certs = server.get_trusted_certs(cred)
1681 if not options.registry_interface:
1682 trusted_certs = ReturnValue.get_value(trusted_certs)
1684 for trusted_cert in trusted_certs:
1685 print "\n===========================================================\n"
1686 gid = GID(string=trusted_cert)
1688 cert = Certificate(string=trusted_cert)
1689 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1690 print "Certificate:\n%s\n\n"%trusted_cert