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
50 DEFAULT_RSPEC_VERSION = "GENI 3"
52 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
53 terminal_render, filter_records
56 def display_rspec(rspec, format='rspec'):
58 tree = etree.parse(StringIO(rspec))
60 result = root.xpath("./network/site/node/hostname/text()")
61 elif format in ['ip']:
62 # The IP address is not yet part of the new RSpec
63 # so this doesn't do anything yet.
64 tree = etree.parse(StringIO(rspec))
66 result = root.xpath("./network/site/node/ipv4/text()")
73 def display_list(results):
74 for result in results:
77 def display_records(recordList, dump=False):
78 ''' Print all fields in the record'''
79 for record in recordList:
80 display_record(record, dump)
82 def display_record(record, dump=False):
84 record.dump(sort=True)
86 info = record.getdict()
87 print "%s (%s)" % (info['hrn'], info['type'])
91 def filter_records(type, records):
93 for record in records:
94 if (record['type'] == type) or (type == "all"):
95 filtered_records.append(record)
96 return filtered_records
99 def credential_printable (cred):
100 credential = Credential(cred=cred)
102 result += credential.pretty_cred()
104 rights = credential.get_privileges()
105 result += "type=%s\n" % credential.type
106 result += "version=%s\n" % credential.version
107 result += "rights=%s\n" % rights
110 def show_credentials (cred_s):
111 if not isinstance (cred_s,list): cred_s = [cred_s]
113 print "Using Credential %s"%credential_printable(cred)
116 def save_raw_to_file(var, filename, format="text", banner=None):
118 # if filename is "-", send it to stdout
121 f = open(filename, "w")
126 elif format == "pickled":
127 f.write(pickle.dumps(var))
128 elif format == "json":
129 if hasattr(json, "dumps"):
130 f.write(json.dumps(var)) # python 2.6
132 f.write(json.write(var)) # python 2.5
134 # this should never happen
135 print "unknown output format", format
137 f.write('\n'+banner+"\n")
139 def save_rspec_to_file(rspec, filename):
140 if not filename.endswith(".rspec"):
141 filename = filename + ".rspec"
142 f = open(filename, 'w')
147 def save_records_to_file(filename, record_dicts, format="xml"):
150 for record_dict in record_dicts:
152 save_record_to_file(filename + "." + str(index), record_dict)
154 save_record_to_file(filename, record_dict)
156 elif format == "xmllist":
157 f = open(filename, "w")
158 f.write("<recordlist>\n")
159 for record_dict in record_dicts:
160 record_obj=Record(dict=record_dict)
161 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
162 f.write("</recordlist>\n")
164 elif format == "hrnlist":
165 f = open(filename, "w")
166 for record_dict in record_dicts:
167 record_obj=Record(dict=record_dict)
168 f.write(record_obj.hrn + "\n")
171 # this should never happen
172 print "unknown output format", format
174 def save_record_to_file(filename, record_dict):
175 record = Record(dict=record_dict)
176 xml = record.save_as_xml()
177 f=codecs.open(filename, encoding='utf-8',mode="w")
182 # minimally check a key argument
183 def check_ssh_key (key):
184 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
185 return re.match(good_ssh_key, key, re.IGNORECASE)
188 def normalize_type (type):
189 if type.startswith('au'):
191 elif type.startswith('us'):
193 elif type.startswith('sl'):
195 elif type.startswith('no'):
200 def load_record_from_opts(options):
202 if hasattr(options, 'type'):
203 options.type = normalize_type(options.type)
204 if hasattr(options, 'xrn') and options.xrn:
205 if hasattr(options, 'type') and options.type:
206 xrn = Xrn(options.xrn, options.type)
208 xrn = Xrn(options.xrn)
209 record_dict['urn'] = xrn.get_urn()
210 record_dict['hrn'] = xrn.get_hrn()
211 record_dict['type'] = xrn.get_type()
212 if hasattr(options, 'key') and options.key:
214 pubkey = open(options.key, 'r').read()
217 if not check_ssh_key (pubkey):
218 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
219 record_dict['reg-keys'] = [pubkey]
220 if hasattr(options, 'slices') and options.slices:
221 record_dict['slices'] = options.slices
222 if hasattr(options, 'reg_researchers') and options.reg_researchers is not None:
223 record_dict['reg-researchers'] = options.reg_researchers
224 if hasattr(options, 'email') and options.email:
225 record_dict['email'] = options.email
226 # authorities can have a name for standalone deployment
227 if hasattr(options, 'name') and options.name:
228 record_dict['name'] = options.name
229 if hasattr(options, 'reg_pis') and options.reg_pis:
230 record_dict['reg-pis'] = options.reg_pis
232 # handle extra settings
233 record_dict.update(options.extras)
235 return Record(dict=record_dict)
237 def load_record_from_file(filename):
238 f=codecs.open(filename, encoding="utf-8", mode="r")
239 xml_string = f.read()
241 return Record(xml=xml_string)
245 def unique_call_id(): return uuid.uuid4().urn
247 ########## a simple model for maintaing 3 doc attributes per command (instead of just one)
248 # essentially for the methods that implement a subcommand like sfi list
249 # we need to keep track of
250 # (*) doc a few lines that tell what it does, still located in __doc__
251 # (*) args_string a simple one-liner that describes mandatory arguments
252 # (*) example well, one or several releant examples
254 # since __doc__ only accounts for one, we use this simple mechanism below
255 # however we keep doc in place for easier migration
257 from functools import wraps
259 # we use a list as well as a dict so we can keep track of the order
263 def declare_command (args_string, example,aliases=None):
265 name=getattr(m,'__name__')
266 doc=getattr(m,'__doc__',"-- missing doc --")
267 doc=doc.strip(" \t\n")
268 commands_list.append(name)
269 # last item is 'canonical' name, so we can know which commands are aliases
270 command_tuple=(doc, args_string, example,name)
271 commands_dict[name]=command_tuple
272 if aliases is not None:
273 for alias in aliases:
274 commands_list.append(alias)
275 commands_dict[alias]=command_tuple
277 def new_method (*args, **kwds): return m(*args, **kwds)
282 def remove_none_fields (record):
283 none_fields=[ k for (k,v) in record.items() if v is None ]
284 for k in none_fields: del record[k]
290 # dirty hack to make this class usable from the outside
291 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
294 def default_sfi_dir ():
295 if os.path.isfile("./sfi_config"):
298 return os.path.expanduser("~/.sfi/")
300 # dummy to meet Sfi's expectations for its 'options' field
301 # i.e. s/t we can do setattr on
305 def __init__ (self,options=None):
306 if options is None: options=Sfi.DummyOptions()
307 for opt in Sfi.required_options:
308 if not hasattr(options,opt): setattr(options,opt,None)
309 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
310 self.options = options
312 self.authority = None
313 self.logger = sfi_logger
314 self.logger.enable_console()
315 ### various auxiliary material that we keep at hand
317 # need to call this other than just 'config' as we have a command/method with that name
318 self.config_instance=None
319 self.config_file=None
320 self.client_bootstrap=None
322 ### suitable if no reasonable command has been provided
323 def print_commands_help (self, options):
324 verbose=getattr(options,'verbose')
325 format3="%10s %-35s %s"
329 print format3%("command","cmd_args","description")
333 self.create_parser_global().print_help()
334 # preserve order from the code
335 for command in commands_list:
337 (doc, args_string, example, canonical) = commands_dict[command]
339 print "Cannot find info on command %s - skipped"%command
343 if command==canonical:
344 doc=doc.replace("\n","\n"+format3offset*' ')
345 print format3%(command,args_string,doc)
347 self.create_parser_command(command).print_help()
349 print format3%(command,"<<alias for %s>>"%canonical,"")
351 ### now if a known command was found we can be more verbose on that one
352 def print_help (self):
353 print "==================== Generic sfi usage"
354 self.sfi_parser.print_help()
355 (doc,_,example,canonical)=commands_dict[self.command]
356 if canonical != self.command:
357 print "\n==================== NOTE: %s is an alias for genuine %s"%(self.command,canonical)
358 self.command=canonical
359 print "\n==================== Purpose of %s"%self.command
361 print "\n==================== Specific usage for %s"%self.command
362 self.command_parser.print_help()
364 print "\n==================== %s example(s)"%self.command
367 def create_parser_global(self):
368 # Generate command line parser
369 parser = OptionParser(add_help_option=False,
370 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
371 description="Commands: %s"%(" ".join(commands_list)))
372 parser.add_option("-r", "--registry", dest="registry",
373 help="root registry", metavar="URL", default=None)
374 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
375 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
376 parser.add_option("-R", "--raw", dest="raw", default=None,
377 help="Save raw, unparsed server response to a file")
378 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
379 help="raw file format ([text]|pickled|json)", default="text",
380 choices=("text","pickled","json"))
381 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
382 help="text string to write before and after raw output")
383 parser.add_option("-d", "--dir", dest="sfi_dir",
384 help="config & working directory - default is %default",
385 metavar="PATH", default=Sfi.default_sfi_dir())
386 parser.add_option("-u", "--user", dest="user",
387 help="user name", metavar="HRN", default=None)
388 parser.add_option("-a", "--auth", dest="auth",
389 help="authority name", metavar="HRN", default=None)
390 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
391 help="verbose mode - cumulative")
392 parser.add_option("-D", "--debug",
393 action="store_true", dest="debug", default=False,
394 help="Debug (xml-rpc) protocol messages")
395 # would it make sense to use ~/.ssh/id_rsa as a default here ?
396 parser.add_option("-k", "--private-key",
397 action="store", dest="user_private_key", default=None,
398 help="point to the private key file to use if not yet installed in sfi_dir")
399 parser.add_option("-t", "--timeout", dest="timeout", default=None,
400 help="Amout of time to wait before timing out the request")
401 parser.add_option("-h", "--help",
402 action="store_true", dest="help", default=False,
403 help="one page summary on commands & exit")
404 parser.disable_interspersed_args()
409 def create_parser_command(self, command):
410 if command not in commands_dict:
411 msg="Invalid command\n"
413 msg += ','.join(commands_list)
414 self.logger.critical(msg)
417 # retrieve args_string
418 (_, args_string, __,canonical) = commands_dict[command]
420 parser = OptionParser(add_help_option=False,
421 usage="sfi [sfi_options] %s [cmd_options] %s"
422 % (command, args_string))
423 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
424 help="Summary of one command usage")
426 if canonical in ("config"):
427 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
428 help='how myslice config variables as well')
430 if canonical in ("version"):
431 parser.add_option("-l","--local",
432 action="store_true", dest="version_local", default=False,
433 help="display version of the local client")
435 if canonical in ("version", "trusted"):
436 parser.add_option("-R","--registry_interface",
437 action="store_true", dest="registry_interface", default=False,
438 help="target the registry interface instead of slice interface")
440 if canonical in ("register", "update"):
441 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
442 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
443 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
444 parser.add_option('-n', '--name', dest='name', default="", help="name (optional for authorities)")
445 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
447 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
448 default='', type="str", action='callback', callback=optparse_listvalue_callback)
449 parser.add_option('-r', '--researchers', dest='reg_researchers', metavar='<researchers>',
450 help='Set/replace slice researchers - use -r none to reset', default=None, type="str", action='callback',
451 callback=optparse_listvalue_callback)
452 parser.add_option('-p', '--pis', dest='reg_pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
453 default='', type="str", action='callback', callback=optparse_listvalue_callback)
454 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
455 action="callback", callback=optparse_dictvalue_callback, nargs=1,
456 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
458 # user specifies remote aggregate/sm/component
459 if canonical in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
460 "action", "shutdown", "renew", "status"):
461 parser.add_option("-d", "--delegate", dest="delegate", default=None,
463 help="Include a credential delegated to the user's root"+\
464 "authority in set of credentials for this call")
466 # show_credential option
467 if canonical in ("list","resources", "describe", "provision", "allocate", "register","update","remove","delete","status","renew"):
468 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
469 help="show credential(s) used in human-readable form")
470 if canonical in ("renew"):
471 parser.add_option("-l","--as-long-as-possible",dest='alap',action='store_true',default=False,
472 help="renew as long as possible")
473 # registy filter option
474 if canonical in ("list", "show", "remove"):
475 parser.add_option("-t", "--type", dest="type", type="choice",
476 help="type filter ([all]|user|slice|authority|node|aggregate)",
477 choices=("all", "user", "slice", "authority", "node", "aggregate"),
479 if canonical in ("show"):
480 parser.add_option("-k","--key",dest="keys",action="append",default=[],
481 help="specify specific keys to be displayed from record")
482 parser.add_option("-n","--no-details",dest="no_details",action="store_true",default=False,
483 help="call Resolve without the 'details' option")
484 if canonical in ("resources", "describe"):
486 parser.add_option("-r", "--rspec-version", dest="rspec_version", default=DEFAULT_RSPEC_VERSION,
487 help="schema type and version of resulting RSpec (default:{})".format(DEFAULT_RSPEC_VERSION))
488 # disable/enable cached rspecs
489 parser.add_option("-c", "--current", dest="current", default=False,
491 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
493 parser.add_option("-f", "--format", dest="format", type="choice",
494 help="display format ([xml]|dns|ip)", default="xml",
495 choices=("xml", "dns", "ip"))
496 #panos: a new option to define the type of information about resources a user is interested in
497 parser.add_option("-i", "--info", dest="info",
498 help="optional component information", default=None)
499 # a new option to retreive or not reservation-oriented RSpecs (leases)
500 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
501 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
502 choices=("all", "resources", "leases"), default="resources")
505 if canonical in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
506 parser.add_option("-o", "--output", dest="file",
507 help="output XML to file", metavar="FILE", default=None)
509 if canonical in ("show", "list"):
510 parser.add_option("-f", "--format", dest="format", type="choice",
511 help="display format ([text]|xml)", default="text",
512 choices=("text", "xml"))
514 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
515 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
516 choices=("xml", "xmllist", "hrnlist"))
517 if canonical == 'list':
518 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
519 help="list all child records", default=False)
520 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
521 help="gives details, like user keys", default=False)
522 if canonical in ("delegate"):
523 parser.add_option("-u", "--user",
524 action="store_true", dest="delegate_user", default=False,
525 help="delegate your own credentials; default if no other option is provided")
526 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
527 metavar="slice_hrn", help="delegate cred. for slice HRN")
528 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
529 metavar='auth_hrn', help="delegate cred for auth HRN")
530 # this primarily is a shorthand for -A my_hrn^
531 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
532 help="delegate your PI credentials, so s.t. like -A your_hrn^")
533 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
534 help="""by default the mandatory argument is expected to be a user,
535 use this if you mean an authority instead""")
537 if canonical in ("myslice"):
538 parser.add_option("-p","--password",dest='password',action='store',default=None,
539 help="specify mainfold password on the command line")
540 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
541 metavar="slice_hrn", help="delegate cred. for slice HRN")
542 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
543 metavar='auth_hrn', help="delegate PI cred for auth HRN")
544 parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
545 parser.add_option('-b', '--backend', dest='backend', help="Override 'backend' from the config file")
551 # Main: parse arguments and dispatch to command
553 def dispatch(self, command, command_options, command_args):
554 (doc, args_string, example, canonical) = commands_dict[command]
555 method=getattr(self, canonical, None)
557 print "sfi: unknown command %s"%command
558 raise SystemExit,"Unknown command %s"%command
559 return method(command_options, command_args)
562 self.sfi_parser = self.create_parser_global()
563 (options, args) = self.sfi_parser.parse_args()
565 self.print_commands_help(options)
567 self.options = options
569 self.logger.setLevelFromOptVerbose(self.options.verbose)
572 self.logger.critical("No command given. Use -h for help.")
573 self.print_commands_help(options)
576 # complete / find unique match with command set
577 command_candidates = Candidates (commands_list)
579 command = command_candidates.only_match(input)
581 self.print_commands_help(options)
583 # second pass options parsing
585 self.command_parser = self.create_parser_command(command)
586 (command_options, command_args) = self.command_parser.parse_args(args[1:])
587 if command_options.help:
590 self.command_options = command_options
594 self.logger.debug("Command=%s" % self.command)
597 retcod = self.dispatch(command, command_options, command_args)
601 self.logger.log_exc ("sfi command %s failed"%command)
606 def read_config(self):
607 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
608 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
610 if Config.is_ini(config_file):
611 config = Config (config_file)
613 # try upgrading from shell config format
614 fp, fn = mkstemp(suffix='sfi_config', text=True)
616 # we need to preload the sections we want parsed
617 # from the shell config
618 config.add_section('sfi')
619 # sface users should be able to use this same file to configure their stuff
620 config.add_section('sface')
621 # manifold users should be able to specify the details
622 # of their backend server here for 'sfi myslice'
623 config.add_section('myslice')
624 config.load(config_file)
626 shutil.move(config_file, shell_config_file)
628 config.save(config_file)
631 self.logger.critical("Failed to read configuration file %s"%config_file)
632 self.logger.info("Make sure to remove the export clauses and to add quotes")
633 if self.options.verbose==0:
634 self.logger.info("Re-run with -v for more details")
636 self.logger.log_exc("Could not read config file %s"%config_file)
639 self.config_instance=config
642 if (self.options.sm is not None):
643 self.sm_url = self.options.sm
644 elif hasattr(config, "SFI_SM"):
645 self.sm_url = config.SFI_SM
647 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
651 if (self.options.registry is not None):
652 self.reg_url = self.options.registry
653 elif hasattr(config, "SFI_REGISTRY"):
654 self.reg_url = config.SFI_REGISTRY
656 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
660 if (self.options.user is not None):
661 self.user = self.options.user
662 elif hasattr(config, "SFI_USER"):
663 self.user = config.SFI_USER
665 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
669 if (self.options.auth is not None):
670 self.authority = self.options.auth
671 elif hasattr(config, "SFI_AUTH"):
672 self.authority = config.SFI_AUTH
674 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
677 self.config_file=config_file
682 # Get various credential and spec files
684 # Establishes limiting conventions
685 # - conflates MAs and SAs
686 # - assumes last token in slice name is unique
688 # Bootstraps credentials
689 # - bootstrap user credential from self-signed certificate
690 # - bootstrap authority credential from user credential
691 # - bootstrap slice credential from user credential
694 # init self-signed cert, user credentials and gid
695 def bootstrap (self):
696 if self.options.verbose:
697 self.logger.info("Initializing SfaClientBootstrap with {}".format(self.reg_url))
698 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
700 # if -k is provided, use this to initialize private key
701 if self.options.user_private_key:
702 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
704 # trigger legacy compat code if needed
705 # the name has changed from just <leaf>.pkey to <hrn>.pkey
706 if not os.path.isfile(client_bootstrap.private_key_filename()):
707 self.logger.info ("private key not found, trying legacy name")
709 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
710 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
711 client_bootstrap.init_private_key_if_missing (legacy_private_key)
712 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
714 self.logger.log_exc("Can't find private key ")
718 client_bootstrap.bootstrap_my_gid()
719 # extract what's needed
720 self.private_key = client_bootstrap.private_key()
721 self.my_credential_string = client_bootstrap.my_credential_string ()
722 self.my_credential = {'geni_type': 'geni_sfa',
724 'geni_value': self.my_credential_string}
725 self.my_gid = client_bootstrap.my_gid ()
726 self.client_bootstrap = client_bootstrap
729 def my_authority_credential_string(self):
730 if not self.authority:
731 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
733 return self.client_bootstrap.authority_credential_string (self.authority)
735 def authority_credential_string(self, auth_hrn):
736 return self.client_bootstrap.authority_credential_string (auth_hrn)
738 def slice_credential_string(self, name):
739 return self.client_bootstrap.slice_credential_string (name)
741 def slice_credential(self, name):
742 return {'geni_type': 'geni_sfa',
744 'geni_value': self.slice_credential_string(name)}
746 # xxx should be supported by sfaclientbootstrap as well
747 def delegate_cred(self, object_cred, hrn, type='authority'):
748 # the gid and hrn of the object we are delegating
749 if isinstance(object_cred, str):
750 object_cred = Credential(string=object_cred)
751 object_gid = object_cred.get_gid_object()
752 object_hrn = object_gid.get_hrn()
754 if not object_cred.get_privileges().get_all_delegate():
755 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
758 # the delegating user's gid
759 caller_gidfile = self.my_gid()
761 # the gid of the user who will be delegated to
762 delegee_gid = self.client_bootstrap.gid(hrn,type)
763 delegee_hrn = delegee_gid.get_hrn()
764 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
765 return dcred.save_to_string(save_parents=True)
768 # Management of the servers
773 if not hasattr (self, 'registry_proxy'):
774 self.logger.info("Contacting Registry at: %s"%self.reg_url)
775 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
776 timeout=self.options.timeout, verbose=self.options.debug)
777 return self.registry_proxy
781 if not hasattr (self, 'sliceapi_proxy'):
782 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
783 if hasattr(self.command_options,'component') and self.command_options.component:
784 # resolve the hrn at the registry
785 node_hrn = self.command_options.component
786 records = self.registry().Resolve(node_hrn, self.my_credential_string)
787 records = filter_records('node', records)
789 self.logger.warning("No such component:%r"% opts.component)
791 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
792 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
794 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
795 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
796 self.sm_url = 'http://' + self.sm_url
797 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
798 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
799 timeout=self.options.timeout, verbose=self.options.debug)
800 return self.sliceapi_proxy
802 def get_cached_server_version(self, server):
803 # check local cache first
806 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
807 cache_key = server.url + "-version"
809 cache = Cache(cache_file)
812 self.logger.info("Local cache not found at: %s" % cache_file)
815 version = cache.get(cache_key)
818 result = server.GetVersion()
819 version= ReturnValue.get_value(result)
820 # cache version for 20 minutes
821 cache.add(cache_key, version, ttl= 60*20)
822 self.logger.info("Updating cache file %s" % cache_file)
823 cache.save_to_file(cache_file)
827 ### resurrect this temporarily so we can support V1 aggregates for a while
828 def server_supports_options_arg(self, server):
830 Returns true if server support the optional call_id arg, false otherwise.
832 server_version = self.get_cached_server_version(server)
834 # xxx need to rewrite this
835 if int(server_version.get('geni_api')) >= 2:
839 def server_supports_call_id_arg(self, server):
840 server_version = self.get_cached_server_version(server)
842 if 'sfa' in server_version and 'code_tag' in server_version:
843 code_tag = server_version['code_tag']
844 code_tag_parts = code_tag.split("-")
845 version_parts = code_tag_parts[0].split(".")
846 major, minor = version_parts[0], version_parts[1]
847 rev = code_tag_parts[1]
848 if int(major) == 1 and minor == 0 and build >= 22:
852 ### ois = options if supported
853 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
854 def ois (self, server, option_dict):
855 if self.server_supports_options_arg (server):
857 elif self.server_supports_call_id_arg (server):
858 return [ unique_call_id () ]
862 ### cis = call_id if supported - like ois
863 def cis (self, server):
864 if self.server_supports_call_id_arg (server):
865 return [ unique_call_id ]
869 ######################################## miscell utilities
870 def get_rspec_file(self, rspec):
871 if (os.path.isabs(rspec)):
874 file = os.path.join(self.options.sfi_dir, rspec)
875 if (os.path.isfile(file)):
878 self.logger.critical("No such rspec file %s"%rspec)
881 def get_record_file(self, record):
882 if (os.path.isabs(record)):
885 file = os.path.join(self.options.sfi_dir, record)
886 if (os.path.isfile(file)):
889 self.logger.critical("No such registry record file %s"%record)
893 # helper function to analyze raw output
894 # for main : return 0 if everything is fine, something else otherwise (mostly 1 for now)
895 def success (self, raw):
896 return_value=ReturnValue (raw)
897 output=ReturnValue.get_output(return_value)
898 # means everything is fine
901 # something went wrong
902 print 'ERROR:',output
905 #==========================================================================
906 # Following functions implement the commands
908 # Registry-related commands
909 #==========================================================================
911 @declare_command("","")
912 def config (self, options, args):
913 "Display contents of current config"
914 print "# From configuration file %s"%self.config_file
915 flags=[ ('sfi', [ ('registry','reg_url'),
916 ('auth','authority'),
922 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
924 for (section, tuples) in flags:
927 for (external_name, internal_name) in tuples:
928 print "%-20s = %s"%(external_name,getattr(self,internal_name))
931 varname="%s_%s"%(section.upper(),name.upper())
932 value=getattr(self.config_instance,varname)
933 print "%-20s = %s"%(name,value)
934 # xxx should analyze result
937 @declare_command("","")
938 def version(self, options, args):
940 display an SFA server version (GetVersion)
941 or version information about sfi itself
943 if options.version_local:
944 version=version_core()
946 if options.registry_interface:
947 server=self.registry()
949 server = self.sliceapi()
950 result = server.GetVersion()
951 version = ReturnValue.get_value(result)
953 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
955 pprinter = PrettyPrinter(indent=4)
956 pprinter.pprint(version)
957 # xxx should analyze result
960 @declare_command("authority","")
961 def list(self, options, args):
963 list entries in named authority registry (List)
970 if options.recursive:
971 opts['recursive'] = options.recursive
973 if options.show_credential:
974 show_credentials(self.my_credential_string)
976 list = self.registry().List(hrn, self.my_credential_string, options)
978 raise Exception, "Not enough parameters for the 'list' command"
980 # filter on person, slice, site, node, etc.
981 # This really should be in the self.filter_records funct def comment...
982 list = filter_records(options.type, list)
983 terminal_render (list, options)
985 save_records_to_file(options.file, list, options.fileformat)
986 # xxx should analyze result
989 @declare_command("name","")
990 def show(self, options, args):
992 show details about named registry record (Resolve)
998 # explicitly require Resolve to run in details mode
1000 if not options.no_details: resolve_options['details']=True
1001 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, resolve_options)
1002 record_dicts = filter_records(options.type, record_dicts)
1003 if not record_dicts:
1004 self.logger.error("No record of type %s"% options.type)
1006 # user has required to focus on some keys
1008 def project (record):
1010 for key in options.keys:
1011 try: projected[key]=record[key]
1014 record_dicts = [ project (record) for record in record_dicts ]
1015 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
1016 for record in records:
1017 if (options.format == "text"): record.dump(sort=True)
1018 else: print record.save_as_xml()
1020 save_records_to_file(options.file, record_dicts, options.fileformat)
1021 # xxx should analyze result
1024 # this historically was named 'add', it is now 'register' with an alias for legacy
1025 @declare_command("[xml-filename]","",['add'])
1026 def register(self, options, args):
1027 """create new record in registry (Register)
1028 from command line options (recommended)
1029 old-school method involving an xml file still supported"""
1031 auth_cred = self.my_authority_credential_string()
1032 if options.show_credential:
1033 show_credentials(auth_cred)
1040 record_filepath = args[0]
1041 rec_file = self.get_record_file(record_filepath)
1042 record_dict.update(load_record_from_file(rec_file).todict())
1044 print "Cannot load record file %s"%record_filepath
1047 record_dict.update(load_record_from_opts(options).todict())
1048 # we should have a type by now
1049 if 'type' not in record_dict :
1052 # this is still planetlab dependent.. as plc will whine without that
1053 # also, it's only for adding
1054 if record_dict['type'] == 'user':
1055 if not 'first_name' in record_dict:
1056 record_dict['first_name'] = record_dict['hrn']
1057 if 'last_name' not in record_dict:
1058 record_dict['last_name'] = record_dict['hrn']
1059 register = self.registry().Register(record_dict, auth_cred)
1060 # xxx looks like the result here is not ReturnValue-compatible
1061 #return self.success (register)
1062 # xxx should analyze result
1065 @declare_command("[xml-filename]","")
1066 def update(self, options, args):
1067 """update record into registry (Update)
1068 from command line options (recommended)
1069 old-school method involving an xml file still supported"""
1072 record_filepath = args[0]
1073 rec_file = self.get_record_file(record_filepath)
1074 record_dict.update(load_record_from_file(rec_file).todict())
1076 record_dict.update(load_record_from_opts(options).todict())
1077 # at the very least we need 'type' here
1078 if 'type' not in record_dict or record_dict['type'] is None:
1082 # don't translate into an object, as this would possibly distort
1083 # user-provided data; e.g. add an 'email' field to Users
1084 if record_dict['type'] in ['user']:
1085 if record_dict['hrn'] == self.user:
1086 cred = self.my_credential_string
1088 cred = self.my_authority_credential_string()
1089 elif record_dict['type'] in ['slice']:
1091 cred = self.slice_credential_string(record_dict['hrn'])
1092 except ServerException, e:
1093 # XXX smbaker -- once we have better error return codes, update this
1094 # to do something better than a string compare
1095 if "Permission error" in e.args[0]:
1096 cred = self.my_authority_credential_string()
1099 elif record_dict['type'] in ['authority']:
1100 cred = self.my_authority_credential_string()
1101 elif record_dict['type'] in ['node']:
1102 cred = self.my_authority_credential_string()
1104 raise Exception("unknown record type {}".format(record_dict['type']))
1105 if options.show_credential:
1106 show_credentials(cred)
1107 update = self.registry().Update(record_dict, cred)
1108 # xxx looks like the result here is not ReturnValue-compatible
1109 #return self.success(update)
1110 # xxx should analyze result
1113 @declare_command("hrn","")
1114 def remove(self, options, args):
1115 "remove registry record by name (Remove)"
1116 auth_cred = self.my_authority_credential_string()
1124 if options.show_credential:
1125 show_credentials(auth_cred)
1126 remove = self.registry().Remove(hrn, auth_cred, type)
1127 # xxx looks like the result here is not ReturnValue-compatible
1128 #return self.success (remove)
1129 # xxx should analyze result
1132 # ==================================================================
1133 # Slice-related commands
1134 # ==================================================================
1136 # show rspec for named slice
1137 @declare_command("","",['discover'])
1138 def resources(self, options, args):
1140 discover available resources (ListResources)
1142 server = self.sliceapi()
1145 creds = [self.my_credential]
1146 if options.delegate:
1147 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1148 if options.show_credential:
1149 show_credentials(creds)
1151 # no need to check if server accepts the options argument since the options has
1152 # been a required argument since v1 API
1154 # always send call_id to v2 servers
1155 api_options ['call_id'] = unique_call_id()
1156 # ask for cached value if available
1157 api_options ['cached'] = True
1159 api_options['info'] = options.info
1160 if options.list_leases:
1161 api_options['list_leases'] = options.list_leases
1163 if options.current == True:
1164 api_options['cached'] = False
1166 api_options['cached'] = True
1167 if options.rspec_version:
1168 version_manager = VersionManager()
1169 server_version = self.get_cached_server_version(server)
1170 if 'sfa' in server_version:
1171 # just request the version the client wants
1172 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1174 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1176 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1177 list_resources = server.ListResources (creds, api_options)
1178 value = ReturnValue.get_value(list_resources)
1179 if self.options.raw:
1180 save_raw_to_file(list_resources, self.options.raw, self.options.rawformat, self.options.rawbanner)
1181 if options.file is not None:
1182 save_rspec_to_file(value, options.file)
1183 if (self.options.raw is None) and (options.file is None):
1184 display_rspec(value, options.format)
1185 return self.success(list_resources)
1187 @declare_command("slice_hrn","")
1188 def describe(self, options, args):
1190 shows currently allocated/provisioned resources
1191 of the named slice or set of slivers (Describe)
1193 server = self.sliceapi()
1196 creds = [self.slice_credential(args[0])]
1197 if options.delegate:
1198 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1199 if options.show_credential:
1200 show_credentials(creds)
1202 api_options = {'call_id': unique_call_id(),
1204 'info': options.info,
1205 'list_leases': options.list_leases,
1206 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1209 api_options['info'] = options.info
1211 if options.rspec_version:
1212 version_manager = VersionManager()
1213 server_version = self.get_cached_server_version(server)
1214 if 'sfa' in server_version:
1215 # just request the version the client wants
1216 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1218 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1219 urn = Xrn(args[0], type='slice').get_urn()
1220 remove_none_fields(api_options)
1221 describe = server.Describe([urn], creds, api_options)
1222 value = ReturnValue.get_value(describe)
1223 if self.options.raw:
1224 save_raw_to_file(describe, self.options.raw, self.options.rawformat, self.options.rawbanner)
1225 if options.file is not None:
1226 save_rspec_to_file(value['geni_rspec'], options.file)
1227 if (self.options.raw is None) and (options.file is None):
1228 display_rspec(value['geni_rspec'], options.format)
1229 return self.success (describe)
1231 @declare_command("slice_hrn [<sliver_urn>...]","")
1232 def delete(self, options, args):
1234 de-allocate and de-provision all or named slivers of the named slice (Delete)
1236 server = self.sliceapi()
1240 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1243 # we have sliver urns
1244 sliver_urns = args[1:]
1246 # we provision all the slivers of the slice
1247 sliver_urns = [slice_urn]
1250 slice_cred = self.slice_credential(slice_hrn)
1251 creds = [slice_cred]
1253 # options and call_id when supported
1255 api_options ['call_id'] = unique_call_id()
1256 if options.show_credential:
1257 show_credentials(creds)
1258 delete = server.Delete(sliver_urns, creds, *self.ois(server, api_options ) )
1259 value = ReturnValue.get_value(delete)
1260 if self.options.raw:
1261 save_raw_to_file(delete, self.options.raw, self.options.rawformat, self.options.rawbanner)
1264 return self.success (delete)
1266 @declare_command("slice_hrn rspec","")
1267 def allocate(self, options, args):
1269 allocate resources to the named slice (Allocate)
1271 server = self.sliceapi()
1272 server_version = self.get_cached_server_version(server)
1274 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1277 creds = [self.slice_credential(slice_hrn)]
1279 delegated_cred = None
1280 if server_version.get('interface') == 'slicemgr':
1281 # delegate our cred to the slice manager
1282 # do not delegate cred to slicemgr...not working at the moment
1284 #if server_version.get('hrn'):
1285 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1286 #elif server_version.get('urn'):
1287 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1289 if options.show_credential:
1290 show_credentials(creds)
1293 rspec_file = self.get_rspec_file(args[1])
1294 rspec = open(rspec_file).read()
1296 api_options ['call_id'] = unique_call_id()
1300 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1301 remove_none_fields(slice_records[0])
1302 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1303 slice_record = slice_records[0]
1304 user_hrns = slice_record['reg-researchers']
1305 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1306 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1307 sfa_users = sfa_users_arg(user_records, slice_record)
1308 geni_users = pg_users_arg(user_records)
1310 api_options['sfa_users'] = sfa_users
1311 api_options['geni_users'] = geni_users
1313 allocate = server.Allocate(slice_urn, creds, rspec, api_options)
1314 value = ReturnValue.get_value(allocate)
1315 if self.options.raw:
1316 save_raw_to_file(allocate, self.options.raw, self.options.rawformat, self.options.rawbanner)
1317 if options.file is not None:
1318 save_rspec_to_file (value['geni_rspec'], options.file)
1319 if (self.options.raw is None) and (options.file is None):
1321 return self.success(allocate)
1323 @declare_command("slice_hrn [<sliver_urn>...]","")
1324 def provision(self, options, args):
1326 provision all or named already allocated slivers of the named slice (Provision)
1328 server = self.sliceapi()
1329 server_version = self.get_cached_server_version(server)
1331 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1333 # we have sliver urns
1334 sliver_urns = args[1:]
1336 # we provision all the slivers of the slice
1337 sliver_urns = [slice_urn]
1340 creds = [self.slice_credential(slice_hrn)]
1341 delegated_cred = None
1342 if server_version.get('interface') == 'slicemgr':
1343 # delegate our cred to the slice manager
1344 # do not delegate cred to slicemgr...not working at the moment
1346 #if server_version.get('hrn'):
1347 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1348 #elif server_version.get('urn'):
1349 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1351 if options.show_credential:
1352 show_credentials(creds)
1355 api_options ['call_id'] = unique_call_id()
1357 # set the requtested rspec version
1358 version_manager = VersionManager()
1359 rspec_version = version_manager._get_version('geni', '3').to_dict()
1360 api_options['geni_rspec_version'] = rspec_version
1363 # need to pass along user keys to the aggregate.
1365 # { urn: urn:publicid:IDN+emulab.net+user+alice
1366 # keys: [<ssh key A>, <ssh key B>]
1369 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1370 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1371 slice_record = slice_records[0]
1372 user_hrns = slice_record['reg-researchers']
1373 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1374 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1375 users = pg_users_arg(user_records)
1377 api_options['geni_users'] = users
1378 provision = server.Provision(sliver_urns, creds, api_options)
1379 value = ReturnValue.get_value(provision)
1380 if self.options.raw:
1381 save_raw_to_file(provision, self.options.raw, self.options.rawformat, self.options.rawbanner)
1382 if options.file is not None:
1383 save_rspec_to_file (value['geni_rspec'], options.file)
1384 if (self.options.raw is None) and (options.file is None):
1386 return self.success(provision)
1388 @declare_command("slice_hrn","")
1389 def status(self, options, args):
1391 retrieve the status of the slivers belonging to the named slice (Status)
1393 server = self.sliceapi()
1397 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1400 slice_cred = self.slice_credential(slice_hrn)
1401 creds = [slice_cred]
1403 # options and call_id when supported
1405 api_options['call_id']=unique_call_id()
1406 if options.show_credential:
1407 show_credentials(creds)
1408 status = server.Status([slice_urn], creds, *self.ois(server,api_options))
1409 value = ReturnValue.get_value(status)
1410 if self.options.raw:
1411 save_raw_to_file(status, self.options.raw, self.options.rawformat, self.options.rawbanner)
1414 return self.success (status)
1416 @declare_command("slice_hrn [<sliver_urn>...] action","")
1417 def action(self, options, args):
1419 Perform the named operational action on all or named slivers of the named slice
1421 server = self.sliceapi()
1425 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1427 # we have sliver urns
1428 sliver_urns = args[1:-1]
1430 # we provision all the slivers of the slice
1431 sliver_urns = [slice_urn]
1434 slice_cred = self.slice_credential(args[0])
1435 creds = [slice_cred]
1436 if options.delegate:
1437 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1438 creds.append(delegated_cred)
1440 perform_action = server.PerformOperationalAction(sliver_urns, creds, action , api_options)
1441 value = ReturnValue.get_value(perform_action)
1442 if self.options.raw:
1443 save_raw_to_file(perform_action, self.options.raw, self.options.rawformat, self.options.rawbanner)
1446 return self.success (perform_action)
1448 @declare_command("slice_hrn [<sliver_urn>...] time",
1449 "\n".join(["sfi renew onelab.ple.heartbeat 2015-04-31",
1450 "sfi renew onelab.ple.heartbeat 2015-04-31T14:00:00Z",
1451 "sfi renew onelab.ple.heartbeat +5d",
1452 "sfi renew onelab.ple.heartbeat +3w",
1453 "sfi renew onelab.ple.heartbeat +2m",]))
1454 def renew(self, options, args):
1458 server = self.sliceapi()
1463 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1466 # we have sliver urns
1467 sliver_urns = args[1:-1]
1469 # we provision all the slivers of the slice
1470 sliver_urns = [slice_urn]
1471 input_time = args[-1]
1473 # time: don't try to be smart on the time format, server-side will
1475 slice_cred = self.slice_credential(args[0])
1476 creds = [slice_cred]
1477 # options and call_id when supported
1479 api_options['call_id']=unique_call_id()
1481 api_options['geni_extend_alap']=True
1482 if options.show_credential:
1483 show_credentials(creds)
1484 renew = server.Renew(sliver_urns, creds, input_time, *self.ois(server,api_options))
1485 value = ReturnValue.get_value(renew)
1486 if self.options.raw:
1487 save_raw_to_file(renew, self.options.raw, self.options.rawformat, self.options.rawbanner)
1490 return self.success(renew)
1492 @declare_command("slice_hrn","")
1493 def shutdown(self, options, args):
1495 shutdown named slice (Shutdown)
1497 server = self.sliceapi()
1500 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1502 slice_cred = self.slice_credential(slice_hrn)
1503 creds = [slice_cred]
1504 shutdown = server.Shutdown(slice_urn, creds)
1505 value = ReturnValue.get_value(shutdown)
1506 if self.options.raw:
1507 save_raw_to_file(shutdown, self.options.raw, self.options.rawformat, self.options.rawbanner)
1510 return self.success (shutdown)
1512 @declare_command("[name]","")
1513 def gid(self, options, args):
1515 Create a GID (CreateGid)
1520 target_hrn = args[0]
1521 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1522 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1524 filename = options.file
1526 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1527 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1528 GID(string=gid).save_to_file(filename)
1529 # xxx should analyze result
1532 ####################
1533 @declare_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1535 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1536 the set of credentials in the scope for this call would be
1537 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1539 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1541 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1542 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1543 because of the two -s options
1546 def delegate (self, options, args):
1548 (locally) create delegate credential for use by given hrn
1549 make sure to check for 'sfi myslice' instead if you plan
1556 # support for several delegations in the same call
1557 # so first we gather the things to do
1559 for slice_hrn in options.delegate_slices:
1560 message="%s.slice"%slice_hrn
1561 original = self.slice_credential_string(slice_hrn)
1562 tuples.append ( (message, original,) )
1563 if options.delegate_pi:
1564 my_authority=self.authority
1565 message="%s.pi"%my_authority
1566 original = self.my_authority_credential_string()
1567 tuples.append ( (message, original,) )
1568 for auth_hrn in options.delegate_auths:
1569 message="%s.auth"%auth_hrn
1570 original=self.authority_credential_string(auth_hrn)
1571 tuples.append ( (message, original, ) )
1572 # if nothing was specified at all at this point, let's assume -u
1573 if not tuples: options.delegate_user=True
1575 if options.delegate_user:
1576 message="%s.user"%self.user
1577 original = self.my_credential_string
1578 tuples.append ( (message, original, ) )
1580 # default type for beneficial is user unless -A
1581 if options.delegate_to_authority: to_type='authority'
1582 else: to_type='user'
1584 # let's now handle all this
1585 # it's all in the filenaming scheme
1586 for (message,original) in tuples:
1587 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1588 delegated_credential = Credential (string=delegated_string)
1589 filename = os.path.join ( self.options.sfi_dir,
1590 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1591 delegated_credential.save_to_file(filename, save_parents=True)
1592 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1594 ####################
1595 @declare_command("","""$ less +/myslice sfi_config
1597 backend = http://manifold.pl.sophia.inria.fr:7080
1598 # the HRN that myslice uses, so that we are delegating to
1599 delegate = ple.upmc.slicebrowser
1600 # platform - this is a myslice concept
1602 # username - as of this writing (May 2013) a simple login name
1606 will first collect the slices that you are part of, then make sure
1607 all your credentials are up-to-date (read: refresh expired ones)
1608 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1609 and upload them all on myslice backend, using 'platform' and 'user'.
1610 A password will be prompted for the upload part.
1612 $ sfi -v myslice -- or sfi -vv myslice
1613 same but with more and more verbosity
1615 $ sfi m -b http://mymanifold.foo.com:7080/
1616 is synonym to sfi myslice as no other command starts with an 'm'
1617 and uses a custom backend for this one call
1620 def myslice (self, options, args):
1622 """ This helper is for refreshing your credentials at myslice; it will
1623 * compute all the slices that you currently have credentials on
1624 * refresh all your credentials (you as a user and pi, your slices)
1625 * upload them to the manifold backend server
1626 for last phase, sfi_config is read to look for the [myslice] section,
1627 and namely the 'backend', 'delegate' and 'user' settings"""
1633 # enable info by default
1634 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1635 ### the rough sketch goes like this
1636 # (0) produce a p12 file
1637 self.client_bootstrap.my_pkcs12()
1639 # (a) rain check for sufficient config in sfi_config
1641 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1642 for key in myslice_keys:
1644 # oct 2013 - I'm finding myself juggling with config files
1645 # so a couple of command-line options can now override config
1646 if hasattr(options,key) and getattr(options,key) is not None:
1647 value=getattr(options,key)
1649 full_key="MYSLICE_" + key.upper()
1650 value=getattr(self.config_instance,full_key,None)
1651 if value: myslice_dict[key]=value
1652 else: print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1653 if len(myslice_dict) != len(myslice_keys):
1656 # (b) figure whether we are PI for the authority where we belong
1657 self.logger.info("Resolving our own id %s"%self.user)
1658 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1659 if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1660 my_record=my_records[0]
1661 my_auths_all = my_record['reg-pi-authorities']
1662 self.logger.info("Found %d authorities that we are PI for"%len(my_auths_all))
1663 self.logger.debug("They are %s"%(my_auths_all))
1665 my_auths = my_auths_all
1666 if options.delegate_auths:
1667 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1668 self.logger.debug("Restricted to user-provided auths"%(my_auths))
1670 # (c) get the set of slices that we are in
1671 my_slices_all=my_record['reg-slices']
1672 self.logger.info("Found %d slices that we are member of"%len(my_slices_all))
1673 self.logger.debug("They are: %s"%(my_slices_all))
1675 my_slices = my_slices_all
1676 # if user provided slices, deal only with these - if they are found
1677 if options.delegate_slices:
1678 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1679 self.logger.debug("Restricted to user-provided slices: %s"%(my_slices))
1681 # (d) make sure we have *valid* credentials for all these
1683 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1684 for auth_hrn in my_auths:
1685 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1686 for slice_hrn in my_slices:
1687 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1689 # (e) check for the delegated version of these
1690 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1691 # switch to myslice using an authority instead of a user
1692 delegatee_type='user'
1693 delegatee_hrn=myslice_dict['delegate']
1694 hrn_delegated_credentials = []
1695 for (hrn, htype, credential) in hrn_credentials:
1696 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1697 # save these so user can monitor what she's uploaded
1698 filename = os.path.join ( self.options.sfi_dir,
1699 "%s.%s_for_%s.%s.cred"%(hrn,htype,delegatee_hrn,delegatee_type))
1700 with file(filename,'w') as f:
1701 f.write(delegated_credential)
1702 self.logger.debug("(Over)wrote %s"%filename)
1703 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1705 # (f) and finally upload them to manifold server
1706 # xxx todo add an option so the password can be set on the command line
1707 # (but *NOT* in the config file) so other apps can leverage this
1708 self.logger.info("Uploading on backend at %s"%myslice_dict['backend'])
1709 uploader = ManifoldUploader (logger=self.logger,
1710 url=myslice_dict['backend'],
1711 platform=myslice_dict['platform'],
1712 username=myslice_dict['username'],
1713 password=options.password)
1714 uploader.prompt_all()
1715 (count_all,count_success)=(0,0)
1716 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1718 inspect=Credential(string=delegated_credential)
1719 expire_datetime=inspect.get_expiration()
1720 message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
1721 if uploader.upload(delegated_credential,message=message):
1724 self.logger.info("Successfully uploaded %d/%d credentials"%(count_success,count_all))
1726 # at first I thought we would want to save these,
1727 # like 'sfi delegate does' but on second thought
1728 # it is probably not helpful as people would not
1729 # need to run 'sfi delegate' at all anymore
1730 if count_success != count_all: sys.exit(1)
1731 # xxx should analyze result
1734 @declare_command("cred","")
1735 def trusted(self, options, args):
1737 return the trusted certs at this interface (get_trusted_certs)
1739 if options.registry_interface:
1740 server=self.registry()
1742 server = self.sliceapi()
1743 cred = self.my_authority_credential_string()
1744 trusted_certs = server.get_trusted_certs(cred)
1745 if not options.registry_interface:
1746 trusted_certs = ReturnValue.get_value(trusted_certs)
1748 for trusted_cert in trusted_certs:
1749 print "\n===========================================================\n"
1750 gid = GID(string=trusted_cert)
1752 cert = Certificate(string=trusted_cert)
1753 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1754 print "Certificate:\n%s\n\n"%trusted_cert
1755 # xxx should analyze result