2 # sfi.py - basic SFA command-line client
3 # this module is also used in sfascan
17 from lxml import etree
18 from StringIO import StringIO
19 from optparse import OptionParser
20 from pprint import PrettyPrinter
21 from tempfile import mkstemp
23 from sfa.trust.certificate import Keypair, Certificate
24 from sfa.trust.gid import GID
25 from sfa.trust.credential import Credential
26 from sfa.trust.sfaticket import SfaTicket
28 from sfa.util.faults import SfaInvalidArgument
29 from sfa.util.sfalogging import sfi_logger
30 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
31 from sfa.util.config import Config
32 from sfa.util.version import version_core
33 from sfa.util.cache import Cache
34 from sfa.util.printable import printable
36 from sfa.storage.record import Record
38 from sfa.rspecs.rspec import RSpec
39 from sfa.rspecs.rspec_converter import RSpecConverter
40 from sfa.rspecs.version_manager import VersionManager
42 from sfa.client.sfaclientlib import SfaClientBootstrap
43 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
44 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
45 from sfa.client.return_value import ReturnValue
46 from sfa.client.candidates import Candidates
47 from sfa.client.manifolduploader import ManifoldUploader
51 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
52 terminal_render, filter_records
55 def display_rspec(rspec, format='rspec'):
57 tree = etree.parse(StringIO(rspec))
59 result = root.xpath("./network/site/node/hostname/text()")
60 elif format in ['ip']:
61 # The IP address is not yet part of the new RSpec
62 # so this doesn't do anything yet.
63 tree = etree.parse(StringIO(rspec))
65 result = root.xpath("./network/site/node/ipv4/text()")
72 def display_list(results):
73 for result in results:
76 def display_records(recordList, dump=False):
77 ''' Print all fields in the record'''
78 for record in recordList:
79 display_record(record, dump)
81 def display_record(record, dump=False):
83 record.dump(sort=True)
85 info = record.getdict()
86 print "%s (%s)" % (info['hrn'], info['type'])
90 def filter_records(type, records):
92 for record in records:
93 if (record['type'] == type) or (type == "all"):
94 filtered_records.append(record)
95 return filtered_records
98 def credential_printable (cred):
99 credential = Credential(cred=cred)
101 result += credential.pretty_cred()
103 rights = credential.get_privileges()
104 result += "type=%s\n" % credential.type
105 result += "version=%s\n" % credential.version
106 result += "rights=%s\n" % rights
109 def show_credentials (cred_s):
110 if not isinstance (cred_s,list): cred_s = [cred_s]
112 print "Using Credential %s"%credential_printable(cred)
115 def save_raw_to_file(var, filename, format="text", banner=None):
117 # if filename is "-", send it to stdout
120 f = open(filename, "w")
125 elif format == "pickled":
126 f.write(pickle.dumps(var))
127 elif format == "json":
128 if hasattr(json, "dumps"):
129 f.write(json.dumps(var)) # python 2.6
131 f.write(json.write(var)) # python 2.5
133 # this should never happen
134 print "unknown output format", format
136 f.write('\n'+banner+"\n")
138 def save_rspec_to_file(rspec, filename):
139 if not filename.endswith(".rspec"):
140 filename = filename + ".rspec"
141 f = open(filename, 'w')
146 def save_records_to_file(filename, record_dicts, format="xml"):
149 for record_dict in record_dicts:
151 save_record_to_file(filename + "." + str(index), record_dict)
153 save_record_to_file(filename, record_dict)
155 elif format == "xmllist":
156 f = open(filename, "w")
157 f.write("<recordlist>\n")
158 for record_dict in record_dicts:
159 record_obj=Record(dict=record_dict)
160 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
161 f.write("</recordlist>\n")
163 elif format == "hrnlist":
164 f = open(filename, "w")
165 for record_dict in record_dicts:
166 record_obj=Record(dict=record_dict)
167 f.write(record_obj.hrn + "\n")
170 # this should never happen
171 print "unknown output format", format
173 def save_record_to_file(filename, record_dict):
174 record = Record(dict=record_dict)
175 xml = record.save_as_xml()
176 f=codecs.open(filename, encoding='utf-8',mode="w")
181 # minimally check a key argument
182 def check_ssh_key (key):
183 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
184 return re.match(good_ssh_key, key, re.IGNORECASE)
187 def 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['reg-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 # authorities can have a name for standalone deployment
212 if hasattr(options, 'name') and options.name:
213 record_dict['name'] = options.name
214 if hasattr(options, 'reg_pis') and options.reg_pis:
215 record_dict['reg-pis'] = options.reg_pis
217 # handle extra settings
218 record_dict.update(options.extras)
220 return Record(dict=record_dict)
222 def load_record_from_file(filename):
223 f=codecs.open(filename, encoding="utf-8", mode="r")
224 xml_string = f.read()
226 return Record(xml=xml_string)
230 def unique_call_id(): return uuid.uuid4().urn
232 ########## a simple model for maintaing 3 doc attributes per command (instead of just one)
233 # essentially for the methods that implement a subcommand like sfi list
234 # we need to keep track of
235 # (*) doc a few lines that tell what it does, still located in __doc__
236 # (*) args_string a simple one-liner that describes mandatory arguments
237 # (*) example well, one or several releant examples
239 # since __doc__ only accounts for one, we use this simple mechanism below
240 # however we keep doc in place for easier migration
242 from functools import wraps
244 # we use a list as well as a dict so we can keep track of the order
248 def declare_command (args_string, example,aliases=None):
250 name=getattr(m,'__name__')
251 doc=getattr(m,'__doc__',"-- missing doc --")
252 doc=doc.strip(" \t\n")
253 commands_list.append(name)
254 # last item is 'canonical' name, so we can know which commands are aliases
255 command_tuple=(doc, args_string, example,name)
256 commands_dict[name]=command_tuple
257 if aliases is not None:
258 for alias in aliases:
259 commands_list.append(alias)
260 commands_dict[alias]=command_tuple
262 def new_method (*args, **kwds): return m(*args, **kwds)
267 def remove_none_fields (record):
268 none_fields=[ k for (k,v) in record.items() if v is None ]
269 for k in none_fields: del record[k]
275 # dirty hack to make this class usable from the outside
276 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
279 def default_sfi_dir ():
280 if os.path.isfile("./sfi_config"):
283 return os.path.expanduser("~/.sfi/")
285 # dummy to meet Sfi's expectations for its 'options' field
286 # i.e. s/t we can do setattr on
290 def __init__ (self,options=None):
291 if options is None: options=Sfi.DummyOptions()
292 for opt in Sfi.required_options:
293 if not hasattr(options,opt): setattr(options,opt,None)
294 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
295 self.options = options
297 self.authority = None
298 self.logger = sfi_logger
299 self.logger.enable_console()
300 ### various auxiliary material that we keep at hand
302 # need to call this other than just 'config' as we have a command/method with that name
303 self.config_instance=None
304 self.config_file=None
305 self.client_bootstrap=None
307 ### suitable if no reasonable command has been provided
308 def print_commands_help (self, options):
309 verbose=getattr(options,'verbose')
310 format3="%10s %-35s %s"
314 print format3%("command","cmd_args","description")
318 self.create_parser_global().print_help()
319 # preserve order from the code
320 for command in commands_list:
322 (doc, args_string, example, canonical) = commands_dict[command]
324 print "Cannot find info on command %s - skipped"%command
328 if command==canonical:
329 doc=doc.replace("\n","\n"+format3offset*' ')
330 print format3%(command,args_string,doc)
332 self.create_parser_command(command).print_help()
334 print format3%(command,"<<alias for %s>>"%canonical,"")
336 ### now if a known command was found we can be more verbose on that one
337 def print_help (self):
338 print "==================== Generic sfi usage"
339 self.sfi_parser.print_help()
340 (doc,_,example,canonical)=commands_dict[self.command]
341 if canonical != self.command:
342 print "\n==================== NOTE: %s is an alias for genuine %s"%(self.command,canonical)
343 self.command=canonical
344 print "\n==================== Purpose of %s"%self.command
346 print "\n==================== Specific usage for %s"%self.command
347 self.command_parser.print_help()
349 print "\n==================== %s example(s)"%self.command
352 def create_parser_global(self):
353 # Generate command line parser
354 parser = OptionParser(add_help_option=False,
355 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
356 description="Commands: %s"%(" ".join(commands_list)))
357 parser.add_option("-r", "--registry", dest="registry",
358 help="root registry", metavar="URL", default=None)
359 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
360 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
361 parser.add_option("-R", "--raw", dest="raw", default=None,
362 help="Save raw, unparsed server response to a file")
363 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
364 help="raw file format ([text]|pickled|json)", default="text",
365 choices=("text","pickled","json"))
366 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
367 help="text string to write before and after raw output")
368 parser.add_option("-d", "--dir", dest="sfi_dir",
369 help="config & working directory - default is %default",
370 metavar="PATH", default=Sfi.default_sfi_dir())
371 parser.add_option("-u", "--user", dest="user",
372 help="user name", metavar="HRN", default=None)
373 parser.add_option("-a", "--auth", dest="auth",
374 help="authority name", metavar="HRN", default=None)
375 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
376 help="verbose mode - cumulative")
377 parser.add_option("-D", "--debug",
378 action="store_true", dest="debug", default=False,
379 help="Debug (xml-rpc) protocol messages")
380 # would it make sense to use ~/.ssh/id_rsa as a default here ?
381 parser.add_option("-k", "--private-key",
382 action="store", dest="user_private_key", default=None,
383 help="point to the private key file to use if not yet installed in sfi_dir")
384 parser.add_option("-t", "--timeout", dest="timeout", default=None,
385 help="Amout of time to wait before timing out the request")
386 parser.add_option("-h", "--help",
387 action="store_true", dest="help", default=False,
388 help="one page summary on commands & exit")
389 parser.disable_interspersed_args()
394 def create_parser_command(self, command):
395 if command not in commands_dict:
396 msg="Invalid command\n"
398 msg += ','.join(commands_list)
399 self.logger.critical(msg)
402 # retrieve args_string
403 (_, args_string, __,canonical) = commands_dict[command]
405 parser = OptionParser(add_help_option=False,
406 usage="sfi [sfi_options] %s [cmd_options] %s"
407 % (command, args_string))
408 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
409 help="Summary of one command usage")
411 if canonical in ("config"):
412 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
413 help='how myslice config variables as well')
415 if canonical in ("version"):
416 parser.add_option("-l","--local",
417 action="store_true", dest="version_local", default=False,
418 help="display version of the local client")
420 if canonical in ("version", "trusted"):
421 parser.add_option("-R","--registry_interface",
422 action="store_true", dest="registry_interface", default=False,
423 help="target the registry interface instead of slice interface")
425 if canonical in ("register", "update"):
426 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
427 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
428 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
429 parser.add_option('-n', '--name', dest='name', default="", help="name (optional for authorities)")
430 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
432 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
433 default='', type="str", action='callback', callback=optparse_listvalue_callback)
434 parser.add_option('-r', '--researchers', dest='reg_researchers', metavar='<researchers>',
435 help='Set/replace slice researchers - use -r none to reset', default=None, type="str", action='callback',
436 callback=optparse_listvalue_callback)
437 parser.add_option('-p', '--pis', dest='reg_pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
438 default='', type="str", action='callback', callback=optparse_listvalue_callback)
439 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
440 action="callback", callback=optparse_dictvalue_callback, nargs=1,
441 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
443 # user specifies remote aggregate/sm/component
444 if canonical in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
445 "action", "shutdown", "renew", "status"):
446 parser.add_option("-d", "--delegate", dest="delegate", default=None,
448 help="Include a credential delegated to the user's root"+\
449 "authority in set of credentials for this call")
451 # show_credential option
452 if canonical in ("list","resources", "describe", "provision", "allocate", "register","update","remove","delete","status","renew"):
453 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
454 help="show credential(s) used in human-readable form")
455 if canonical in ("renew"):
456 parser.add_option("-l","--as-long-as-possible",dest='alap',action='store_true',default=False,
457 help="renew as long as possible")
458 # registy filter option
459 if canonical in ("list", "show", "remove"):
460 parser.add_option("-t", "--type", dest="type", type="choice",
461 help="type filter ([all]|user|slice|authority|node|aggregate)",
462 choices=("all", "user", "slice", "authority", "node", "aggregate"),
464 if canonical in ("show"):
465 parser.add_option("-k","--key",dest="keys",action="append",default=[],
466 help="specify specific keys to be displayed from record")
467 parser.add_option("-n","--no-details",dest="no_details",action="store_true",default=False,
468 help="call Resolve without the 'details' option")
469 if canonical in ("resources", "describe"):
471 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="GENI 3",
472 help="schema type and version of resulting RSpec")
473 # disable/enable cached rspecs
474 parser.add_option("-c", "--current", dest="current", default=False,
476 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
478 parser.add_option("-f", "--format", dest="format", type="choice",
479 help="display format ([xml]|dns|ip)", default="xml",
480 choices=("xml", "dns", "ip"))
481 #panos: a new option to define the type of information about resources a user is interested in
482 parser.add_option("-i", "--info", dest="info",
483 help="optional component information", default=None)
484 # a new option to retreive or not reservation-oriented RSpecs (leases)
485 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
486 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
487 choices=("all", "resources", "leases"), default="resources")
490 if canonical in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
491 parser.add_option("-o", "--output", dest="file",
492 help="output XML to file", metavar="FILE", default=None)
494 if canonical in ("show", "list"):
495 parser.add_option("-f", "--format", dest="format", type="choice",
496 help="display format ([text]|xml)", default="text",
497 choices=("text", "xml"))
499 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
500 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
501 choices=("xml", "xmllist", "hrnlist"))
502 if canonical == 'list':
503 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
504 help="list all child records", default=False)
505 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
506 help="gives details, like user keys", default=False)
507 if canonical in ("delegate"):
508 parser.add_option("-u", "--user",
509 action="store_true", dest="delegate_user", default=False,
510 help="delegate your own credentials; default if no other option is provided")
511 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
512 metavar="slice_hrn", help="delegate cred. for slice HRN")
513 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
514 metavar='auth_hrn', help="delegate cred for auth HRN")
515 # this primarily is a shorthand for -A my_hrn^
516 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
517 help="delegate your PI credentials, so s.t. like -A your_hrn^")
518 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
519 help="""by default the mandatory argument is expected to be a user,
520 use this if you mean an authority instead""")
522 if canonical in ("myslice"):
523 parser.add_option("-p","--password",dest='password',action='store',default=None,
524 help="specify mainfold password on the command line")
525 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
526 metavar="slice_hrn", help="delegate cred. for slice HRN")
527 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
528 metavar='auth_hrn', help="delegate PI cred for auth HRN")
529 parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
530 parser.add_option('-b', '--backend', dest='backend', help="Override 'backend' from the config file")
536 # Main: parse arguments and dispatch to command
538 def dispatch(self, command, command_options, command_args):
539 (doc, args_string, example, canonical) = commands_dict[command]
540 method=getattr(self, canonical, None)
542 print "sfi: unknown command %s"%command
543 raise SystemExit,"Unknown command %s"%command
544 return method(command_options, command_args)
547 self.sfi_parser = self.create_parser_global()
548 (options, args) = self.sfi_parser.parse_args()
550 self.print_commands_help(options)
552 self.options = options
554 self.logger.setLevelFromOptVerbose(self.options.verbose)
557 self.logger.critical("No command given. Use -h for help.")
558 self.print_commands_help(options)
561 # complete / find unique match with command set
562 command_candidates = Candidates (commands_list)
564 command = command_candidates.only_match(input)
566 self.print_commands_help(options)
568 # second pass options parsing
570 self.command_parser = self.create_parser_command(command)
571 (command_options, command_args) = self.command_parser.parse_args(args[1:])
572 if command_options.help:
575 self.command_options = command_options
579 self.logger.debug("Command=%s" % self.command)
582 retcod = self.dispatch(command, command_options, command_args)
586 self.logger.log_exc ("sfi command %s failed"%command)
591 def read_config(self):
592 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
593 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
595 if Config.is_ini(config_file):
596 config = Config (config_file)
598 # try upgrading from shell config format
599 fp, fn = mkstemp(suffix='sfi_config', text=True)
601 # we need to preload the sections we want parsed
602 # from the shell config
603 config.add_section('sfi')
604 # sface users should be able to use this same file to configure their stuff
605 config.add_section('sface')
606 # manifold users should be able to specify the details
607 # of their backend server here for 'sfi myslice'
608 config.add_section('myslice')
609 config.load(config_file)
611 shutil.move(config_file, shell_config_file)
613 config.save(config_file)
616 self.logger.critical("Failed to read configuration file %s"%config_file)
617 self.logger.info("Make sure to remove the export clauses and to add quotes")
618 if self.options.verbose==0:
619 self.logger.info("Re-run with -v for more details")
621 self.logger.log_exc("Could not read config file %s"%config_file)
624 self.config_instance=config
627 if (self.options.sm is not None):
628 self.sm_url = self.options.sm
629 elif hasattr(config, "SFI_SM"):
630 self.sm_url = config.SFI_SM
632 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
636 if (self.options.registry is not None):
637 self.reg_url = self.options.registry
638 elif hasattr(config, "SFI_REGISTRY"):
639 self.reg_url = config.SFI_REGISTRY
641 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
645 if (self.options.user is not None):
646 self.user = self.options.user
647 elif hasattr(config, "SFI_USER"):
648 self.user = config.SFI_USER
650 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
654 if (self.options.auth is not None):
655 self.authority = self.options.auth
656 elif hasattr(config, "SFI_AUTH"):
657 self.authority = config.SFI_AUTH
659 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
662 self.config_file=config_file
667 # Get various credential and spec files
669 # Establishes limiting conventions
670 # - conflates MAs and SAs
671 # - assumes last token in slice name is unique
673 # Bootstraps credentials
674 # - bootstrap user credential from self-signed certificate
675 # - bootstrap authority credential from user credential
676 # - bootstrap slice credential from user credential
679 # init self-signed cert, user credentials and gid
680 def bootstrap (self):
681 if self.options.verbose:
682 self.logger.info("Initializing SfaClientBootstrap with {}".format(self.reg_url))
683 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
685 # if -k is provided, use this to initialize private key
686 if self.options.user_private_key:
687 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
689 # trigger legacy compat code if needed
690 # the name has changed from just <leaf>.pkey to <hrn>.pkey
691 if not os.path.isfile(client_bootstrap.private_key_filename()):
692 self.logger.info ("private key not found, trying legacy name")
694 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
695 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
696 client_bootstrap.init_private_key_if_missing (legacy_private_key)
697 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
699 self.logger.log_exc("Can't find private key ")
703 client_bootstrap.bootstrap_my_gid()
704 # extract what's needed
705 self.private_key = client_bootstrap.private_key()
706 self.my_credential_string = client_bootstrap.my_credential_string ()
707 self.my_credential = {'geni_type': 'geni_sfa',
709 'geni_value': self.my_credential_string}
710 self.my_gid = client_bootstrap.my_gid ()
711 self.client_bootstrap = client_bootstrap
714 def my_authority_credential_string(self):
715 if not self.authority:
716 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
718 return self.client_bootstrap.authority_credential_string (self.authority)
720 def authority_credential_string(self, auth_hrn):
721 return self.client_bootstrap.authority_credential_string (auth_hrn)
723 def slice_credential_string(self, name):
724 return self.client_bootstrap.slice_credential_string (name)
726 def slice_credential(self, name):
727 return {'geni_type': 'geni_sfa',
729 'geni_value': self.slice_credential_string(name)}
731 # xxx should be supported by sfaclientbootstrap as well
732 def delegate_cred(self, object_cred, hrn, type='authority'):
733 # the gid and hrn of the object we are delegating
734 if isinstance(object_cred, str):
735 object_cred = Credential(string=object_cred)
736 object_gid = object_cred.get_gid_object()
737 object_hrn = object_gid.get_hrn()
739 if not object_cred.get_privileges().get_all_delegate():
740 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
743 # the delegating user's gid
744 caller_gidfile = self.my_gid()
746 # the gid of the user who will be delegated to
747 delegee_gid = self.client_bootstrap.gid(hrn,type)
748 delegee_hrn = delegee_gid.get_hrn()
749 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
750 return dcred.save_to_string(save_parents=True)
753 # Management of the servers
758 if not hasattr (self, 'registry_proxy'):
759 self.logger.info("Contacting Registry at: %s"%self.reg_url)
760 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
761 timeout=self.options.timeout, verbose=self.options.debug)
762 return self.registry_proxy
766 if not hasattr (self, 'sliceapi_proxy'):
767 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
768 if hasattr(self.command_options,'component') and self.command_options.component:
769 # resolve the hrn at the registry
770 node_hrn = self.command_options.component
771 records = self.registry().Resolve(node_hrn, self.my_credential_string)
772 records = filter_records('node', records)
774 self.logger.warning("No such component:%r"% opts.component)
776 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
777 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
779 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
780 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
781 self.sm_url = 'http://' + self.sm_url
782 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
783 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
784 timeout=self.options.timeout, verbose=self.options.debug)
785 return self.sliceapi_proxy
787 def get_cached_server_version(self, server):
788 # check local cache first
791 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
792 cache_key = server.url + "-version"
794 cache = Cache(cache_file)
797 self.logger.info("Local cache not found at: %s" % cache_file)
800 version = cache.get(cache_key)
803 result = server.GetVersion()
804 version= ReturnValue.get_value(result)
805 # cache version for 20 minutes
806 cache.add(cache_key, version, ttl= 60*20)
807 self.logger.info("Updating cache file %s" % cache_file)
808 cache.save_to_file(cache_file)
812 ### resurrect this temporarily so we can support V1 aggregates for a while
813 def server_supports_options_arg(self, server):
815 Returns true if server support the optional call_id arg, false otherwise.
817 server_version = self.get_cached_server_version(server)
819 # xxx need to rewrite this
820 if int(server_version.get('geni_api')) >= 2:
824 def server_supports_call_id_arg(self, server):
825 server_version = self.get_cached_server_version(server)
827 if 'sfa' in server_version and 'code_tag' in server_version:
828 code_tag = server_version['code_tag']
829 code_tag_parts = code_tag.split("-")
830 version_parts = code_tag_parts[0].split(".")
831 major, minor = version_parts[0], version_parts[1]
832 rev = code_tag_parts[1]
833 if int(major) == 1 and minor == 0 and build >= 22:
837 ### ois = options if supported
838 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
839 def ois (self, server, option_dict):
840 if self.server_supports_options_arg (server):
842 elif self.server_supports_call_id_arg (server):
843 return [ unique_call_id () ]
847 ### cis = call_id if supported - like ois
848 def cis (self, server):
849 if self.server_supports_call_id_arg (server):
850 return [ unique_call_id ]
854 ######################################## miscell utilities
855 def get_rspec_file(self, rspec):
856 if (os.path.isabs(rspec)):
859 file = os.path.join(self.options.sfi_dir, rspec)
860 if (os.path.isfile(file)):
863 self.logger.critical("No such rspec file %s"%rspec)
866 def get_record_file(self, record):
867 if (os.path.isabs(record)):
870 file = os.path.join(self.options.sfi_dir, record)
871 if (os.path.isfile(file)):
874 self.logger.critical("No such registry record file %s"%record)
878 # helper function to analyze raw output
879 # for main : return 0 if everything is fine, something else otherwise (mostly 1 for now)
880 def success (self, raw):
881 return_value=ReturnValue (raw)
882 output=ReturnValue.get_output(return_value)
883 # means everything is fine
886 # something went wrong
887 print 'ERROR:',output
890 #==========================================================================
891 # Following functions implement the commands
893 # Registry-related commands
894 #==========================================================================
896 @declare_command("","")
897 def config (self, options, args):
898 "Display contents of current config"
899 print "# From configuration file %s"%self.config_file
900 flags=[ ('sfi', [ ('registry','reg_url'),
901 ('auth','authority'),
907 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
909 for (section, tuples) in flags:
912 for (external_name, internal_name) in tuples:
913 print "%-20s = %s"%(external_name,getattr(self,internal_name))
916 varname="%s_%s"%(section.upper(),name.upper())
917 value=getattr(self.config_instance,varname)
918 print "%-20s = %s"%(name,value)
919 # xxx should analyze result
922 @declare_command("","")
923 def version(self, options, args):
925 display an SFA server version (GetVersion)
926 or version information about sfi itself
928 if options.version_local:
929 version=version_core()
931 if options.registry_interface:
932 server=self.registry()
934 server = self.sliceapi()
935 result = server.GetVersion()
936 version = ReturnValue.get_value(result)
938 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
940 pprinter = PrettyPrinter(indent=4)
941 pprinter.pprint(version)
942 # xxx should analyze result
945 @declare_command("authority","")
946 def list(self, options, args):
948 list entries in named authority registry (List)
955 if options.recursive:
956 opts['recursive'] = options.recursive
958 if options.show_credential:
959 show_credentials(self.my_credential_string)
961 list = self.registry().List(hrn, self.my_credential_string, options)
963 raise Exception, "Not enough parameters for the 'list' command"
965 # filter on person, slice, site, node, etc.
966 # This really should be in the self.filter_records funct def comment...
967 list = filter_records(options.type, list)
968 terminal_render (list, options)
970 save_records_to_file(options.file, list, options.fileformat)
971 # xxx should analyze result
974 @declare_command("name","")
975 def show(self, options, args):
977 show details about named registry record (Resolve)
983 # explicitly require Resolve to run in details mode
985 if not options.no_details: resolve_options['details']=True
986 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, resolve_options)
987 record_dicts = filter_records(options.type, record_dicts)
989 self.logger.error("No record of type %s"% options.type)
991 # user has required to focus on some keys
993 def project (record):
995 for key in options.keys:
996 try: projected[key]=record[key]
999 record_dicts = [ project (record) for record in record_dicts ]
1000 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
1001 for record in records:
1002 if (options.format == "text"): record.dump(sort=True)
1003 else: print record.save_as_xml()
1005 save_records_to_file(options.file, record_dicts, options.fileformat)
1006 # xxx should analyze result
1009 # this historically was named 'add', it is now 'register' with an alias for legacy
1010 @declare_command("[xml-filename]","",['add'])
1011 def register(self, options, args):
1012 """create new record in registry (Register)
1013 from command line options (recommended)
1014 old-school method involving an xml file still supported"""
1016 auth_cred = self.my_authority_credential_string()
1017 if options.show_credential:
1018 show_credentials(auth_cred)
1025 record_filepath = args[0]
1026 rec_file = self.get_record_file(record_filepath)
1027 record_dict.update(load_record_from_file(rec_file).todict())
1029 print "Cannot load record file %s"%record_filepath
1032 record_dict.update(load_record_from_opts(options).todict())
1033 # we should have a type by now
1034 if 'type' not in record_dict :
1037 # this is still planetlab dependent.. as plc will whine without that
1038 # also, it's only for adding
1039 if record_dict['type'] == 'user':
1040 if not 'first_name' in record_dict:
1041 record_dict['first_name'] = record_dict['hrn']
1042 if 'last_name' not in record_dict:
1043 record_dict['last_name'] = record_dict['hrn']
1044 register = self.registry().Register(record_dict, auth_cred)
1045 # xxx looks like the result here is not ReturnValue-compatible
1046 #return self.success (register)
1047 # xxx should analyze result
1050 @declare_command("[xml-filename]","")
1051 def update(self, options, args):
1052 """update record into registry (Update)
1053 from command line options (recommended)
1054 old-school method involving an xml file still supported"""
1057 record_filepath = args[0]
1058 rec_file = self.get_record_file(record_filepath)
1059 record_dict.update(load_record_from_file(rec_file).todict())
1061 record_dict.update(load_record_from_opts(options).todict())
1062 # at the very least we need 'type' here
1063 if 'type' not in record_dict:
1067 # don't translate into an object, as this would possibly distort
1068 # user-provided data; e.g. add an 'email' field to Users
1069 if record_dict['type'] in ['user']:
1070 if record_dict['hrn'] == self.user:
1071 cred = self.my_credential_string
1073 cred = self.my_authority_credential_string()
1074 elif record_dict['type'] in ['slice']:
1076 cred = self.slice_credential_string(record_dict['hrn'])
1077 except ServerException, e:
1078 # XXX smbaker -- once we have better error return codes, update this
1079 # to do something better than a string compare
1080 if "Permission error" in e.args[0]:
1081 cred = self.my_authority_credential_string()
1084 elif record_dict['type'] in ['authority']:
1085 cred = self.my_authority_credential_string()
1086 elif record_dict['type'] in ['node']:
1087 cred = self.my_authority_credential_string()
1089 raise "unknown record type" + record_dict['type']
1090 if options.show_credential:
1091 show_credentials(cred)
1092 update = self.registry().Update(record_dict, cred)
1093 # xxx looks like the result here is not ReturnValue-compatible
1094 #return self.success(update)
1095 # xxx should analyze result
1098 @declare_command("hrn","")
1099 def remove(self, options, args):
1100 "remove registry record by name (Remove)"
1101 auth_cred = self.my_authority_credential_string()
1109 if options.show_credential:
1110 show_credentials(auth_cred)
1111 remove = self.registry().Remove(hrn, auth_cred, type)
1112 # xxx looks like the result here is not ReturnValue-compatible
1113 #return self.success (remove)
1114 # xxx should analyze result
1117 # ==================================================================
1118 # Slice-related commands
1119 # ==================================================================
1121 # show rspec for named slice
1122 @declare_command("","")
1123 def resources(self, options, args):
1125 discover available resources (ListResources)
1127 server = self.sliceapi()
1130 creds = [self.my_credential]
1131 if options.delegate:
1132 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1133 if options.show_credential:
1134 show_credentials(creds)
1136 # no need to check if server accepts the options argument since the options has
1137 # been a required argument since v1 API
1139 # always send call_id to v2 servers
1140 api_options ['call_id'] = unique_call_id()
1141 # ask for cached value if available
1142 api_options ['cached'] = True
1144 api_options['info'] = options.info
1145 if options.list_leases:
1146 api_options['list_leases'] = options.list_leases
1148 if options.current == True:
1149 api_options['cached'] = False
1151 api_options['cached'] = True
1152 if options.rspec_version:
1153 version_manager = VersionManager()
1154 server_version = self.get_cached_server_version(server)
1155 if 'sfa' in server_version:
1156 # just request the version the client wants
1157 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1159 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1161 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1162 list_resources = server.ListResources (creds, api_options)
1163 value = ReturnValue.get_value(list_resources)
1164 if self.options.raw:
1165 save_raw_to_file(list_resources, self.options.raw, self.options.rawformat, self.options.rawbanner)
1166 if options.file is not None:
1167 save_rspec_to_file(value, options.file)
1168 if (self.options.raw is None) and (options.file is None):
1169 display_rspec(value, options.format)
1170 return self.success(list_resources)
1172 @declare_command("slice_hrn","")
1173 def describe(self, options, args):
1175 shows currently allocated/provisioned resources
1176 of the named slice or set of slivers (Describe)
1178 server = self.sliceapi()
1181 creds = [self.slice_credential(args[0])]
1182 if options.delegate:
1183 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1184 if options.show_credential:
1185 show_credentials(creds)
1187 api_options = {'call_id': unique_call_id(),
1189 'info': options.info,
1190 'list_leases': options.list_leases,
1191 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1194 api_options['info'] = options.info
1196 if options.rspec_version:
1197 version_manager = VersionManager()
1198 server_version = self.get_cached_server_version(server)
1199 if 'sfa' in server_version:
1200 # just request the version the client wants
1201 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1203 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1204 urn = Xrn(args[0], type='slice').get_urn()
1205 remove_none_fields(api_options)
1206 describe = server.Describe([urn], creds, api_options)
1207 value = ReturnValue.get_value(describe)
1208 if self.options.raw:
1209 save_raw_to_file(describe, self.options.raw, self.options.rawformat, self.options.rawbanner)
1210 if options.file is not None:
1211 save_rspec_to_file(value['geni_rspec'], options.file)
1212 if (self.options.raw is None) and (options.file is None):
1213 display_rspec(value['geni_rspec'], options.format)
1214 return self.success (describe)
1216 @declare_command("slice_hrn [<sliver_urn>...]","")
1217 def delete(self, options, args):
1219 de-allocate and de-provision all or named slivers of the named slice (Delete)
1221 server = self.sliceapi()
1225 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1228 # we have sliver urns
1229 sliver_urns = args[1:]
1231 # we provision all the slivers of the slice
1232 sliver_urns = [slice_urn]
1235 slice_cred = self.slice_credential(slice_hrn)
1236 creds = [slice_cred]
1238 # options and call_id when supported
1240 api_options ['call_id'] = unique_call_id()
1241 if options.show_credential:
1242 show_credentials(creds)
1243 delete = server.Delete(sliver_urns, creds, *self.ois(server, api_options ) )
1244 value = ReturnValue.get_value(delete)
1245 if self.options.raw:
1246 save_raw_to_file(delete, self.options.raw, self.options.rawformat, self.options.rawbanner)
1249 return self.success (delete)
1251 @declare_command("slice_hrn rspec","")
1252 def allocate(self, options, args):
1254 allocate resources to the named slice (Allocate)
1256 server = self.sliceapi()
1257 server_version = self.get_cached_server_version(server)
1259 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1262 creds = [self.slice_credential(slice_hrn)]
1264 delegated_cred = None
1265 if server_version.get('interface') == 'slicemgr':
1266 # delegate our cred to the slice manager
1267 # do not delegate cred to slicemgr...not working at the moment
1269 #if server_version.get('hrn'):
1270 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1271 #elif server_version.get('urn'):
1272 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1274 if options.show_credential:
1275 show_credentials(creds)
1278 rspec_file = self.get_rspec_file(args[1])
1279 rspec = open(rspec_file).read()
1281 api_options ['call_id'] = unique_call_id()
1285 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1286 remove_none_fields(slice_records[0])
1287 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1288 slice_record = slice_records[0]
1289 user_hrns = slice_record['reg-researchers']
1290 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1291 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1292 sfa_users = sfa_users_arg(user_records, slice_record)
1293 geni_users = pg_users_arg(user_records)
1295 api_options['sfa_users'] = sfa_users
1296 api_options['geni_users'] = geni_users
1298 allocate = server.Allocate(slice_urn, creds, rspec, api_options)
1299 value = ReturnValue.get_value(allocate)
1300 if self.options.raw:
1301 save_raw_to_file(allocate, self.options.raw, self.options.rawformat, self.options.rawbanner)
1302 if options.file is not None:
1303 save_rspec_to_file (value['geni_rspec'], options.file)
1304 if (self.options.raw is None) and (options.file is None):
1306 return self.success(allocate)
1308 @declare_command("slice_hrn [<sliver_urn>...]","")
1309 def provision(self, options, args):
1311 provision all or named already allocated slivers of the named slice (Provision)
1313 server = self.sliceapi()
1314 server_version = self.get_cached_server_version(server)
1316 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1318 # we have sliver urns
1319 sliver_urns = args[1:]
1321 # we provision all the slivers of the slice
1322 sliver_urns = [slice_urn]
1325 creds = [self.slice_credential(slice_hrn)]
1326 delegated_cred = None
1327 if server_version.get('interface') == 'slicemgr':
1328 # delegate our cred to the slice manager
1329 # do not delegate cred to slicemgr...not working at the moment
1331 #if server_version.get('hrn'):
1332 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1333 #elif server_version.get('urn'):
1334 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1336 if options.show_credential:
1337 show_credentials(creds)
1340 api_options ['call_id'] = unique_call_id()
1342 # set the requtested rspec version
1343 version_manager = VersionManager()
1344 rspec_version = version_manager._get_version('geni', '3').to_dict()
1345 api_options['geni_rspec_version'] = rspec_version
1348 # need to pass along user keys to the aggregate.
1350 # { urn: urn:publicid:IDN+emulab.net+user+alice
1351 # keys: [<ssh key A>, <ssh key B>]
1354 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1355 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1356 slice_record = slice_records[0]
1357 user_hrns = slice_record['reg-researchers']
1358 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1359 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1360 users = pg_users_arg(user_records)
1362 api_options['geni_users'] = users
1363 provision = server.Provision(sliver_urns, creds, api_options)
1364 value = ReturnValue.get_value(provision)
1365 if self.options.raw:
1366 save_raw_to_file(provision, self.options.raw, self.options.rawformat, self.options.rawbanner)
1367 if options.file is not None:
1368 save_rspec_to_file (value['geni_rspec'], options.file)
1369 if (self.options.raw is None) and (options.file is None):
1371 return self.success(provision)
1373 @declare_command("slice_hrn","")
1374 def status(self, options, args):
1376 retrieve the status of the slivers belonging to the named slice (Status)
1378 server = self.sliceapi()
1382 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1385 slice_cred = self.slice_credential(slice_hrn)
1386 creds = [slice_cred]
1388 # options and call_id when supported
1390 api_options['call_id']=unique_call_id()
1391 if options.show_credential:
1392 show_credentials(creds)
1393 status = server.Status([slice_urn], creds, *self.ois(server,api_options))
1394 value = ReturnValue.get_value(status)
1395 if self.options.raw:
1396 save_raw_to_file(status, self.options.raw, self.options.rawformat, self.options.rawbanner)
1399 return self.success (status)
1401 @declare_command("slice_hrn [<sliver_urn>...] action","")
1402 def action(self, options, args):
1404 Perform the named operational action on all or named slivers of the named slice
1406 server = self.sliceapi()
1410 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]
1419 slice_cred = self.slice_credential(args[0])
1420 creds = [slice_cred]
1421 if options.delegate:
1422 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1423 creds.append(delegated_cred)
1425 perform_action = server.PerformOperationalAction(sliver_urns, creds, action , api_options)
1426 value = ReturnValue.get_value(perform_action)
1427 if self.options.raw:
1428 save_raw_to_file(perform_action, self.options.raw, self.options.rawformat, self.options.rawbanner)
1431 return self.success (perform_action)
1433 @declare_command("slice_hrn [<sliver_urn>...] time",
1434 "\n".join(["sfi renew onelab.ple.heartbeat 2015-04-31",
1435 "sfi renew onelab.ple.heartbeat 2015-04-31T14:00:00Z",
1436 "sfi renew onelab.ple.heartbeat +5d",
1437 "sfi renew onelab.ple.heartbeat +3w",
1438 "sfi renew onelab.ple.heartbeat +2m",]))
1439 def renew(self, options, args):
1443 server = self.sliceapi()
1448 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1451 # we have sliver urns
1452 sliver_urns = args[1:-1]
1454 # we provision all the slivers of the slice
1455 sliver_urns = [slice_urn]
1456 input_time = args[-1]
1458 # time: don't try to be smart on the time format, server-side will
1460 slice_cred = self.slice_credential(args[0])
1461 creds = [slice_cred]
1462 # options and call_id when supported
1464 api_options['call_id']=unique_call_id()
1466 api_options['geni_extend_alap']=True
1467 if options.show_credential:
1468 show_credentials(creds)
1469 renew = server.Renew(sliver_urns, creds, input_time, *self.ois(server,api_options))
1470 value = ReturnValue.get_value(renew)
1471 if self.options.raw:
1472 save_raw_to_file(renew, self.options.raw, self.options.rawformat, self.options.rawbanner)
1475 return self.success(renew)
1477 @declare_command("slice_hrn","")
1478 def shutdown(self, options, args):
1480 shutdown named slice (Shutdown)
1482 server = self.sliceapi()
1485 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1487 slice_cred = self.slice_credential(slice_hrn)
1488 creds = [slice_cred]
1489 shutdown = server.Shutdown(slice_urn, creds)
1490 value = ReturnValue.get_value(shutdown)
1491 if self.options.raw:
1492 save_raw_to_file(shutdown, self.options.raw, self.options.rawformat, self.options.rawbanner)
1495 return self.success (shutdown)
1497 @declare_command("[name]","")
1498 def gid(self, options, args):
1500 Create a GID (CreateGid)
1505 target_hrn = args[0]
1506 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1507 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1509 filename = options.file
1511 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1512 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1513 GID(string=gid).save_to_file(filename)
1514 # xxx should analyze result
1517 ####################
1518 @declare_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1520 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1521 the set of credentials in the scope for this call would be
1522 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1524 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1526 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1527 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1528 because of the two -s options
1531 def delegate (self, options, args):
1533 (locally) create delegate credential for use by given hrn
1534 make sure to check for 'sfi myslice' instead if you plan
1541 # support for several delegations in the same call
1542 # so first we gather the things to do
1544 for slice_hrn in options.delegate_slices:
1545 message="%s.slice"%slice_hrn
1546 original = self.slice_credential_string(slice_hrn)
1547 tuples.append ( (message, original,) )
1548 if options.delegate_pi:
1549 my_authority=self.authority
1550 message="%s.pi"%my_authority
1551 original = self.my_authority_credential_string()
1552 tuples.append ( (message, original,) )
1553 for auth_hrn in options.delegate_auths:
1554 message="%s.auth"%auth_hrn
1555 original=self.authority_credential_string(auth_hrn)
1556 tuples.append ( (message, original, ) )
1557 # if nothing was specified at all at this point, let's assume -u
1558 if not tuples: options.delegate_user=True
1560 if options.delegate_user:
1561 message="%s.user"%self.user
1562 original = self.my_credential_string
1563 tuples.append ( (message, original, ) )
1565 # default type for beneficial is user unless -A
1566 if options.delegate_to_authority: to_type='authority'
1567 else: to_type='user'
1569 # let's now handle all this
1570 # it's all in the filenaming scheme
1571 for (message,original) in tuples:
1572 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1573 delegated_credential = Credential (string=delegated_string)
1574 filename = os.path.join ( self.options.sfi_dir,
1575 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1576 delegated_credential.save_to_file(filename, save_parents=True)
1577 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1579 ####################
1580 @declare_command("","""$ less +/myslice sfi_config
1582 backend = http://manifold.pl.sophia.inria.fr:7080
1583 # the HRN that myslice uses, so that we are delegating to
1584 delegate = ple.upmc.slicebrowser
1585 # platform - this is a myslice concept
1587 # username - as of this writing (May 2013) a simple login name
1591 will first collect the slices that you are part of, then make sure
1592 all your credentials are up-to-date (read: refresh expired ones)
1593 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1594 and upload them all on myslice backend, using 'platform' and 'user'.
1595 A password will be prompted for the upload part.
1597 $ sfi -v myslice -- or sfi -vv myslice
1598 same but with more and more verbosity
1600 $ sfi m -b http://mymanifold.foo.com:7080/
1601 is synonym to sfi myslice as no other command starts with an 'm'
1602 and uses a custom backend for this one call
1605 def myslice (self, options, args):
1607 """ This helper is for refreshing your credentials at myslice; it will
1608 * compute all the slices that you currently have credentials on
1609 * refresh all your credentials (you as a user and pi, your slices)
1610 * upload them to the manifold backend server
1611 for last phase, sfi_config is read to look for the [myslice] section,
1612 and namely the 'backend', 'delegate' and 'user' settings"""
1618 # enable info by default
1619 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1620 ### the rough sketch goes like this
1621 # (0) produce a p12 file
1622 self.client_bootstrap.my_pkcs12()
1624 # (a) rain check for sufficient config in sfi_config
1626 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1627 for key in myslice_keys:
1629 # oct 2013 - I'm finding myself juggling with config files
1630 # so a couple of command-line options can now override config
1631 if hasattr(options,key) and getattr(options,key) is not None:
1632 value=getattr(options,key)
1634 full_key="MYSLICE_" + key.upper()
1635 value=getattr(self.config_instance,full_key,None)
1636 if value: myslice_dict[key]=value
1637 else: print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1638 if len(myslice_dict) != len(myslice_keys):
1641 # (b) figure whether we are PI for the authority where we belong
1642 self.logger.info("Resolving our own id %s"%self.user)
1643 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1644 if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1645 my_record=my_records[0]
1646 my_auths_all = my_record['reg-pi-authorities']
1647 self.logger.info("Found %d authorities that we are PI for"%len(my_auths_all))
1648 self.logger.debug("They are %s"%(my_auths_all))
1650 my_auths = my_auths_all
1651 if options.delegate_auths:
1652 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1653 self.logger.debug("Restricted to user-provided auths"%(my_auths))
1655 # (c) get the set of slices that we are in
1656 my_slices_all=my_record['reg-slices']
1657 self.logger.info("Found %d slices that we are member of"%len(my_slices_all))
1658 self.logger.debug("They are: %s"%(my_slices_all))
1660 my_slices = my_slices_all
1661 # if user provided slices, deal only with these - if they are found
1662 if options.delegate_slices:
1663 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1664 self.logger.debug("Restricted to user-provided slices: %s"%(my_slices))
1666 # (d) make sure we have *valid* credentials for all these
1668 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1669 for auth_hrn in my_auths:
1670 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1671 for slice_hrn in my_slices:
1672 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1674 # (e) check for the delegated version of these
1675 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1676 # switch to myslice using an authority instead of a user
1677 delegatee_type='user'
1678 delegatee_hrn=myslice_dict['delegate']
1679 hrn_delegated_credentials = []
1680 for (hrn, htype, credential) in hrn_credentials:
1681 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1682 # save these so user can monitor what she's uploaded
1683 filename = os.path.join ( self.options.sfi_dir,
1684 "%s.%s_for_%s.%s.cred"%(hrn,htype,delegatee_hrn,delegatee_type))
1685 with file(filename,'w') as f:
1686 f.write(delegated_credential)
1687 self.logger.debug("(Over)wrote %s"%filename)
1688 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1690 # (f) and finally upload them to manifold server
1691 # xxx todo add an option so the password can be set on the command line
1692 # (but *NOT* in the config file) so other apps can leverage this
1693 self.logger.info("Uploading on backend at %s"%myslice_dict['backend'])
1694 uploader = ManifoldUploader (logger=self.logger,
1695 url=myslice_dict['backend'],
1696 platform=myslice_dict['platform'],
1697 username=myslice_dict['username'],
1698 password=options.password)
1699 uploader.prompt_all()
1700 (count_all,count_success)=(0,0)
1701 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1703 inspect=Credential(string=delegated_credential)
1704 expire_datetime=inspect.get_expiration()
1705 message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
1706 if uploader.upload(delegated_credential,message=message):
1709 self.logger.info("Successfully uploaded %d/%d credentials"%(count_success,count_all))
1711 # at first I thought we would want to save these,
1712 # like 'sfi delegate does' but on second thought
1713 # it is probably not helpful as people would not
1714 # need to run 'sfi delegate' at all anymore
1715 if count_success != count_all: sys.exit(1)
1716 # xxx should analyze result
1719 @declare_command("cred","")
1720 def trusted(self, options, args):
1722 return the trusted certs at this interface (get_trusted_certs)
1724 if options.registry_interface:
1725 server=self.registry()
1727 server = self.sliceapi()
1728 cred = self.my_authority_credential_string()
1729 trusted_certs = server.get_trusted_certs(cred)
1730 if not options.registry_interface:
1731 trusted_certs = ReturnValue.get_value(trusted_certs)
1733 for trusted_cert in trusted_certs:
1734 print "\n===========================================================\n"
1735 gid = GID(string=trusted_cert)
1737 cert = Certificate(string=trusted_cert)
1738 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1739 print "Certificate:\n%s\n\n"%trusted_cert
1740 # xxx should analyze result