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
35 from sfa.storage.record import Record
37 from sfa.rspecs.rspec import RSpec
38 from sfa.rspecs.rspec_converter import RSpecConverter
39 from sfa.rspecs.version_manager import VersionManager
41 from sfa.client.sfaclientlib import SfaClientBootstrap
42 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
43 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
44 from sfa.client.return_value import ReturnValue
45 from sfa.client.candidates import Candidates
46 from sfa.client.manifolduploader import ManifoldUploader
50 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
51 terminal_render, filter_records
54 def display_rspec(rspec, format='rspec'):
56 tree = etree.parse(StringIO(rspec))
58 result = root.xpath("./network/site/node/hostname/text()")
59 elif format in ['ip']:
60 # The IP address is not yet part of the new RSpec
61 # so this doesn't do anything yet.
62 tree = etree.parse(StringIO(rspec))
64 result = root.xpath("./network/site/node/ipv4/text()")
71 def display_list(results):
72 for result in results:
75 def display_records(recordList, dump=False):
76 ''' Print all fields in the record'''
77 for record in recordList:
78 display_record(record, dump)
80 def display_record(record, dump=False):
82 record.dump(sort=True)
84 info = record.getdict()
85 print "%s (%s)" % (info['hrn'], info['type'])
89 def filter_records(type, records):
91 for record in records:
92 if (record['type'] == type) or (type == "all"):
93 filtered_records.append(record)
94 return filtered_records
97 def credential_printable (cred):
98 credential=Credential(cred=cred)
100 result += credential.get_summary_tostring()
102 rights = credential.get_privileges()
103 result += "type=%s\n" % credential.type
104 result += "version=%s\n" % credential.version
105 result += "rights=%s\n"%rights
108 def show_credentials (cred_s):
109 if not isinstance (cred_s,list): cred_s = [cred_s]
111 print "Using Credential %s"%credential_printable(cred)
114 def save_raw_to_file(var, filename, format="text", banner=None):
116 # if filename is "-", send it to stdout
119 f = open(filename, "w")
124 elif format == "pickled":
125 f.write(pickle.dumps(var))
126 elif format == "json":
127 if hasattr(json, "dumps"):
128 f.write(json.dumps(var)) # python 2.6
130 f.write(json.write(var)) # python 2.5
132 # this should never happen
133 print "unknown output format", format
135 f.write('\n'+banner+"\n")
137 def save_rspec_to_file(rspec, filename):
138 if not filename.endswith(".rspec"):
139 filename = filename + ".rspec"
140 f = open(filename, 'w')
145 def save_records_to_file(filename, record_dicts, format="xml"):
148 for record_dict in record_dicts:
150 save_record_to_file(filename + "." + str(index), record_dict)
152 save_record_to_file(filename, record_dict)
154 elif format == "xmllist":
155 f = open(filename, "w")
156 f.write("<recordlist>\n")
157 for record_dict in record_dicts:
158 record_obj=Record(dict=record_dict)
159 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
160 f.write("</recordlist>\n")
162 elif format == "hrnlist":
163 f = open(filename, "w")
164 for record_dict in record_dicts:
165 record_obj=Record(dict=record_dict)
166 f.write(record_obj.hrn + "\n")
169 # this should never happen
170 print "unknown output format", format
172 def save_record_to_file(filename, record_dict):
173 record = Record(dict=record_dict)
174 xml = record.save_as_xml()
175 f=codecs.open(filename, encoding='utf-8',mode="w")
180 # minimally check a key argument
181 def check_ssh_key (key):
182 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
183 return re.match(good_ssh_key, key, re.IGNORECASE)
186 def load_record_from_opts(options):
188 if hasattr(options, 'xrn') and options.xrn:
189 if hasattr(options, 'type') and options.type:
190 xrn = Xrn(options.xrn, options.type)
192 xrn = Xrn(options.xrn)
193 record_dict['urn'] = xrn.get_urn()
194 record_dict['hrn'] = xrn.get_hrn()
195 record_dict['type'] = xrn.get_type()
196 if hasattr(options, 'key') and options.key:
198 pubkey = open(options.key, 'r').read()
201 if not check_ssh_key (pubkey):
202 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
203 record_dict['keys'] = [pubkey]
204 if hasattr(options, 'slices') and options.slices:
205 record_dict['slices'] = options.slices
206 if hasattr(options, 'researchers') and options.researchers is not None:
207 record_dict['researcher'] = options.researchers
208 if hasattr(options, 'email') and options.email:
209 record_dict['email'] = options.email
210 if hasattr(options, 'pis') and options.pis:
211 record_dict['pi'] = options.pis
213 # handle extra settings
214 record_dict.update(options.extras)
216 return Record(dict=record_dict)
218 def load_record_from_file(filename):
219 f=codecs.open(filename, encoding="utf-8", mode="r")
220 xml_string = f.read()
222 return Record(xml=xml_string)
226 def unique_call_id(): return uuid.uuid4().urn
228 ########## a simple model for maintaing 3 doc attributes per command (instead of just one)
229 # essentially for the methods that implement a subcommand like sfi list
230 # we need to keep track of
231 # (*) doc a few lines that tell what it does, still located in __doc__
232 # (*) args_string a simple one-liner that describes mandatory arguments
233 # (*) example well, one or several releant examples
235 # since __doc__ only accounts for one, we use this simple mechanism below
236 # however we keep doc in place for easier migration
238 from functools import wraps
240 # we use a list as well as a dict so we can keep track of the order
244 def declare_command (args_string, example,aliases=None):
246 name=getattr(m,'__name__')
247 doc=getattr(m,'__doc__',"-- missing doc --")
248 doc=doc.strip(" \t\n")
249 commands_list.append(name)
250 # last item is 'canonical' name, so we can know which commands are aliases
251 command_tuple=(doc, args_string, example,name)
252 commands_dict[name]=command_tuple
253 if aliases is not None:
254 for alias in aliases:
255 commands_list.append(alias)
256 commands_dict[alias]=command_tuple
258 def new_method (*args, **kwds): return m(*args, **kwds)
266 # dirty hack to make this class usable from the outside
267 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
270 def default_sfi_dir ():
271 if os.path.isfile("./sfi_config"):
274 return os.path.expanduser("~/.sfi/")
276 # dummy to meet Sfi's expectations for its 'options' field
277 # i.e. s/t we can do setattr on
281 def __init__ (self,options=None):
282 if options is None: options=Sfi.DummyOptions()
283 for opt in Sfi.required_options:
284 if not hasattr(options,opt): setattr(options,opt,None)
285 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
286 self.options = options
288 self.authority = None
289 self.logger = sfi_logger
290 self.logger.enable_console()
291 ### various auxiliary material that we keep at hand
293 # need to call this other than just 'config' as we have a command/method with that name
294 self.config_instance=None
295 self.config_file=None
296 self.client_bootstrap=None
298 ### suitable if no reasonable command has been provided
299 def print_commands_help (self, options):
300 verbose=getattr(options,'verbose')
301 format3="%10s %-35s %s"
305 print format3%("command","cmd_args","description")
309 self.create_parser_global().print_help()
310 # preserve order from the code
311 for command in commands_list:
313 (doc, args_string, example, canonical) = commands_dict[command]
315 print "Cannot find info on command %s - skipped"%command
319 if command==canonical:
320 doc=doc.replace("\n","\n"+format3offset*' ')
321 print format3%(command,args_string,doc)
323 self.create_parser_command(command).print_help()
325 print format3%(command,"<<alias for %s>>"%canonical,"")
327 ### now if a known command was found we can be more verbose on that one
328 def print_help (self):
329 print "==================== Generic sfi usage"
330 self.sfi_parser.print_help()
331 (doc,_,example,canonical)=commands_dict[self.command]
332 if canonical != self.command:
333 print "\n==================== NOTE: %s is an alias for genuine %s"%(self.command,canonical)
334 self.command=canonical
335 print "\n==================== Purpose of %s"%self.command
337 print "\n==================== Specific usage for %s"%self.command
338 self.command_parser.print_help()
340 print "\n==================== %s example(s)"%self.command
343 def create_parser_global(self):
344 # Generate command line parser
345 parser = OptionParser(add_help_option=False,
346 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
347 description="Commands: %s"%(" ".join(commands_list)))
348 parser.add_option("-r", "--registry", dest="registry",
349 help="root registry", metavar="URL", default=None)
350 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
351 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
352 parser.add_option("-R", "--raw", dest="raw", default=None,
353 help="Save raw, unparsed server response to a file")
354 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
355 help="raw file format ([text]|pickled|json)", default="text",
356 choices=("text","pickled","json"))
357 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
358 help="text string to write before and after raw output")
359 parser.add_option("-d", "--dir", dest="sfi_dir",
360 help="config & working directory - default is %default",
361 metavar="PATH", default=Sfi.default_sfi_dir())
362 parser.add_option("-u", "--user", dest="user",
363 help="user name", metavar="HRN", default=None)
364 parser.add_option("-a", "--auth", dest="auth",
365 help="authority name", metavar="HRN", default=None)
366 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
367 help="verbose mode - cumulative")
368 parser.add_option("-D", "--debug",
369 action="store_true", dest="debug", default=False,
370 help="Debug (xml-rpc) protocol messages")
371 # would it make sense to use ~/.ssh/id_rsa as a default here ?
372 parser.add_option("-k", "--private-key",
373 action="store", dest="user_private_key", default=None,
374 help="point to the private key file to use if not yet installed in sfi_dir")
375 parser.add_option("-t", "--timeout", dest="timeout", default=None,
376 help="Amout of time to wait before timing out the request")
377 parser.add_option("-h", "--help",
378 action="store_true", dest="help", default=False,
379 help="one page summary on commands & exit")
380 parser.disable_interspersed_args()
385 def create_parser_command(self, command):
386 if command not in commands_dict:
387 msg="Invalid command\n"
389 msg += ','.join(commands_list)
390 self.logger.critical(msg)
393 # retrieve args_string
394 (_, args_string, __,___) = commands_dict[command]
396 parser = OptionParser(add_help_option=False,
397 usage="sfi [sfi_options] %s [cmd_options] %s"
398 % (command, args_string))
399 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
400 help="Summary of one command usage")
402 if command in ("config"):
403 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
404 help='how myslice config variables as well')
406 if command in ("version"):
407 parser.add_option("-l","--local",
408 action="store_true", dest="version_local", default=False,
409 help="display version of the local client")
411 if command in ("version", "trusted"):
412 parser.add_option("-R","--registry_interface",
413 action="store_true", dest="registry_interface", default=False,
414 help="target the registry interface instead of slice interface")
416 if command in ("register", "update"):
417 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
418 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
419 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
420 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
422 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
423 default='', type="str", action='callback', callback=optparse_listvalue_callback)
424 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
425 help='Set/replace slice researchers - use -r none to reset', default='', type="str", action='callback',
426 callback=optparse_listvalue_callback)
427 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
428 default='', type="str", action='callback', callback=optparse_listvalue_callback)
429 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
430 action="callback", callback=optparse_dictvalue_callback, nargs=1,
431 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
433 # user specifies remote aggregate/sm/component
434 if command in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
435 "action", "shutdown", "renew", "status"):
436 parser.add_option("-d", "--delegate", dest="delegate", default=None,
438 help="Include a credential delegated to the user's root"+\
439 "authority in set of credentials for this call")
441 # show_credential option
442 if command in ("list","resources", "describe", "provision", "allocate", "register","update","remove","delete","status","renew"):
443 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
444 help="show credential(s) used in human-readable form")
445 # registy filter option
446 if command in ("list", "show", "remove"):
447 parser.add_option("-t", "--type", dest="type", type="choice",
448 help="type filter ([all]|user|slice|authority|node|aggregate)",
449 choices=("all", "user", "slice", "authority", "node", "aggregate"),
451 if command in ("show"):
452 parser.add_option("-k","--key",dest="keys",action="append",default=[],
453 help="specify specific keys to be displayed from record")
454 parser.add_option("-n","--no-details",dest="no_details",action="store_true",default=False,
455 help="call Resolve without the 'details' option")
456 if command in ("resources", "describe"):
458 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="GENI 3",
459 help="schema type and version of resulting RSpec")
460 # disable/enable cached rspecs
461 parser.add_option("-c", "--current", dest="current", default=False,
463 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
465 parser.add_option("-f", "--format", dest="format", type="choice",
466 help="display format ([xml]|dns|ip)", default="xml",
467 choices=("xml", "dns", "ip"))
468 #panos: a new option to define the type of information about resources a user is interested in
469 parser.add_option("-i", "--info", dest="info",
470 help="optional component information", default=None)
471 # a new option to retreive or not reservation-oriented RSpecs (leases)
472 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
473 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
474 choices=("all", "resources", "leases"), default="resources")
477 if command in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
478 parser.add_option("-o", "--output", dest="file",
479 help="output XML to file", metavar="FILE", default=None)
481 if command in ("show", "list"):
482 parser.add_option("-f", "--format", dest="format", type="choice",
483 help="display format ([text]|xml)", default="text",
484 choices=("text", "xml"))
486 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
487 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
488 choices=("xml", "xmllist", "hrnlist"))
489 if command == 'list':
490 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
491 help="list all child records", default=False)
492 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
493 help="gives details, like user keys", default=False)
494 if command in ("delegate"):
495 parser.add_option("-u", "--user",
496 action="store_true", dest="delegate_user", default=False,
497 help="delegate your own credentials; default if no other option is provided")
498 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
499 metavar="slice_hrn", help="delegate cred. for slice HRN")
500 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
501 metavar='auth_hrn', help="delegate cred for auth HRN")
502 # this primarily is a shorthand for -a my_hrn
503 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
504 help="delegate your PI credentials, so s.t. like -a your_hrn^")
505 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
506 help="""by default the mandatory argument is expected to be a user,
507 use this if you mean an authority instead""")
509 if command in ("myslice"):
510 parser.add_option("-p","--password",dest='password',action='store',default=None,
511 help="specify mainfold password on the command line")
512 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
513 metavar="slice_hrn", help="delegate cred. for slice HRN")
514 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
515 metavar='auth_hrn', help="delegate PI cred for auth HRN")
516 parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
517 parser.add_option('-b', '--backend', dest='backend', help="Override 'backend' from the config file")
523 # Main: parse arguments and dispatch to command
525 def dispatch(self, command, command_options, command_args):
526 method=getattr(self, command, None)
528 print "Unknown command %s"%command
530 return method(command_options, command_args)
533 self.sfi_parser = self.create_parser_global()
534 (options, args) = self.sfi_parser.parse_args()
536 self.print_commands_help(options)
538 self.options = options
540 self.logger.setLevelFromOptVerbose(self.options.verbose)
543 self.logger.critical("No command given. Use -h for help.")
544 self.print_commands_help(options)
547 # complete / find unique match with command set
548 command_candidates = Candidates (commands_list)
550 command = command_candidates.only_match(input)
552 self.print_commands_help(options)
554 # second pass options parsing
556 self.command_parser = self.create_parser_command(command)
557 (command_options, command_args) = self.command_parser.parse_args(args[1:])
558 if command_options.help:
561 self.command_options = command_options
565 self.logger.debug("Command=%s" % self.command)
568 self.dispatch(command, command_options, command_args)
572 self.logger.log_exc ("sfi command %s failed"%command)
578 def read_config(self):
579 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
580 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
582 if Config.is_ini(config_file):
583 config = Config (config_file)
585 # try upgrading from shell config format
586 fp, fn = mkstemp(suffix='sfi_config', text=True)
588 # we need to preload the sections we want parsed
589 # from the shell config
590 config.add_section('sfi')
591 # sface users should be able to use this same file to configure their stuff
592 config.add_section('sface')
593 # manifold users should be able to specify the details
594 # of their backend server here for 'sfi myslice'
595 config.add_section('myslice')
596 config.load(config_file)
598 shutil.move(config_file, shell_config_file)
600 config.save(config_file)
603 self.logger.critical("Failed to read configuration file %s"%config_file)
604 self.logger.info("Make sure to remove the export clauses and to add quotes")
605 if self.options.verbose==0:
606 self.logger.info("Re-run with -v for more details")
608 self.logger.log_exc("Could not read config file %s"%config_file)
611 self.config_instance=config
614 if (self.options.sm is not None):
615 self.sm_url = self.options.sm
616 elif hasattr(config, "SFI_SM"):
617 self.sm_url = config.SFI_SM
619 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
623 if (self.options.registry is not None):
624 self.reg_url = self.options.registry
625 elif hasattr(config, "SFI_REGISTRY"):
626 self.reg_url = config.SFI_REGISTRY
628 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
632 if (self.options.user is not None):
633 self.user = self.options.user
634 elif hasattr(config, "SFI_USER"):
635 self.user = config.SFI_USER
637 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
641 if (self.options.auth is not None):
642 self.authority = self.options.auth
643 elif hasattr(config, "SFI_AUTH"):
644 self.authority = config.SFI_AUTH
646 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
649 self.config_file=config_file
654 # Get various credential and spec files
656 # Establishes limiting conventions
657 # - conflates MAs and SAs
658 # - assumes last token in slice name is unique
660 # Bootstraps credentials
661 # - bootstrap user credential from self-signed certificate
662 # - bootstrap authority credential from user credential
663 # - bootstrap slice credential from user credential
666 # init self-signed cert, user credentials and gid
667 def bootstrap (self):
668 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
670 # if -k is provided, use this to initialize private key
671 if self.options.user_private_key:
672 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
674 # trigger legacy compat code if needed
675 # the name has changed from just <leaf>.pkey to <hrn>.pkey
676 if not os.path.isfile(client_bootstrap.private_key_filename()):
677 self.logger.info ("private key not found, trying legacy name")
679 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
680 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
681 client_bootstrap.init_private_key_if_missing (legacy_private_key)
682 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
684 self.logger.log_exc("Can't find private key ")
688 client_bootstrap.bootstrap_my_gid()
689 # extract what's needed
690 self.private_key = client_bootstrap.private_key()
691 self.my_credential_string = client_bootstrap.my_credential_string ()
692 self.my_credential = {'geni_type': 'geni_sfa',
694 'geni_value': self.my_credential_string}
695 self.my_gid = client_bootstrap.my_gid ()
696 self.client_bootstrap = client_bootstrap
699 def my_authority_credential_string(self):
700 if not self.authority:
701 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
703 return self.client_bootstrap.authority_credential_string (self.authority)
705 def authority_credential_string(self, auth_hrn):
706 return self.client_bootstrap.authority_credential_string (auth_hrn)
708 def slice_credential_string(self, name):
709 return self.client_bootstrap.slice_credential_string (name)
711 def slice_credential(self, name):
712 return {'geni_type': 'geni_sfa',
714 'geni_value': self.slice_credential_string(name)}
716 # xxx should be supported by sfaclientbootstrap as well
717 def delegate_cred(self, object_cred, hrn, type='authority'):
718 # the gid and hrn of the object we are delegating
719 if isinstance(object_cred, str):
720 object_cred = Credential(string=object_cred)
721 object_gid = object_cred.get_gid_object()
722 object_hrn = object_gid.get_hrn()
724 if not object_cred.get_privileges().get_all_delegate():
725 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
728 # the delegating user's gid
729 caller_gidfile = self.my_gid()
731 # the gid of the user who will be delegated to
732 delegee_gid = self.client_bootstrap.gid(hrn,type)
733 delegee_hrn = delegee_gid.get_hrn()
734 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
735 return dcred.save_to_string(save_parents=True)
738 # Management of the servers
743 if not hasattr (self, 'registry_proxy'):
744 self.logger.info("Contacting Registry at: %s"%self.reg_url)
745 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
746 timeout=self.options.timeout, verbose=self.options.debug)
747 return self.registry_proxy
751 if not hasattr (self, 'sliceapi_proxy'):
752 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
753 if hasattr(self.command_options,'component') and self.command_options.component:
754 # resolve the hrn at the registry
755 node_hrn = self.command_options.component
756 records = self.registry().Resolve(node_hrn, self.my_credential_string)
757 records = filter_records('node', records)
759 self.logger.warning("No such component:%r"% opts.component)
761 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
762 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
764 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
765 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
766 self.sm_url = 'http://' + self.sm_url
767 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
768 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
769 timeout=self.options.timeout, verbose=self.options.debug)
770 return self.sliceapi_proxy
772 def get_cached_server_version(self, server):
773 # check local cache first
776 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
777 cache_key = server.url + "-version"
779 cache = Cache(cache_file)
782 self.logger.info("Local cache not found at: %s" % cache_file)
785 version = cache.get(cache_key)
788 result = server.GetVersion()
789 version= ReturnValue.get_value(result)
790 # cache version for 20 minutes
791 cache.add(cache_key, version, ttl= 60*20)
792 self.logger.info("Updating cache file %s" % cache_file)
793 cache.save_to_file(cache_file)
797 ### resurrect this temporarily so we can support V1 aggregates for a while
798 def server_supports_options_arg(self, server):
800 Returns true if server support the optional call_id arg, false otherwise.
802 server_version = self.get_cached_server_version(server)
804 # xxx need to rewrite this
805 if int(server_version.get('geni_api')) >= 2:
809 def server_supports_call_id_arg(self, server):
810 server_version = self.get_cached_server_version(server)
812 if 'sfa' in server_version and 'code_tag' in server_version:
813 code_tag = server_version['code_tag']
814 code_tag_parts = code_tag.split("-")
815 version_parts = code_tag_parts[0].split(".")
816 major, minor = version_parts[0], version_parts[1]
817 rev = code_tag_parts[1]
818 if int(major) == 1 and minor == 0 and build >= 22:
822 ### ois = options if supported
823 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
824 def ois (self, server, option_dict):
825 if self.server_supports_options_arg (server):
827 elif self.server_supports_call_id_arg (server):
828 return [ unique_call_id () ]
832 ### cis = call_id if supported - like ois
833 def cis (self, server):
834 if self.server_supports_call_id_arg (server):
835 return [ unique_call_id ]
839 ######################################## miscell utilities
840 def get_rspec_file(self, rspec):
841 if (os.path.isabs(rspec)):
844 file = os.path.join(self.options.sfi_dir, rspec)
845 if (os.path.isfile(file)):
848 self.logger.critical("No such rspec file %s"%rspec)
851 def get_record_file(self, record):
852 if (os.path.isabs(record)):
855 file = os.path.join(self.options.sfi_dir, record)
856 if (os.path.isfile(file)):
859 self.logger.critical("No such registry record file %s"%record)
863 #==========================================================================
864 # Following functions implement the commands
866 # Registry-related commands
867 #==========================================================================
869 @declare_command("","")
870 def config (self, options, args):
871 "Display contents of current config"
872 print "# From configuration file %s"%self.config_file
873 flags=[ ('sfi', [ ('registry','reg_url'),
874 ('auth','authority'),
880 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
882 for (section, tuples) in flags:
885 for (external_name, internal_name) in tuples:
886 print "%-20s = %s"%(external_name,getattr(self,internal_name))
889 varname="%s_%s"%(section.upper(),name.upper())
890 value=getattr(self.config_instance,varname)
891 print "%-20s = %s"%(name,value)
893 @declare_command("","")
894 def version(self, options, args):
896 display an SFA server version (GetVersion)
897 or version information about sfi itself
899 if options.version_local:
900 version=version_core()
902 if options.registry_interface:
903 server=self.registry()
905 server = self.sliceapi()
906 result = server.GetVersion()
907 version = ReturnValue.get_value(result)
909 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
911 pprinter = PrettyPrinter(indent=4)
912 pprinter.pprint(version)
914 @declare_command("authority","")
915 def list(self, options, args):
917 list entries in named authority registry (List)
924 if options.recursive:
925 opts['recursive'] = options.recursive
927 if options.show_credential:
928 show_credentials(self.my_credential_string)
930 list = self.registry().List(hrn, self.my_credential_string, options)
932 raise Exception, "Not enough parameters for the 'list' command"
934 # filter on person, slice, site, node, etc.
935 # This really should be in the self.filter_records funct def comment...
936 list = filter_records(options.type, list)
937 terminal_render (list, options)
939 save_records_to_file(options.file, list, options.fileformat)
942 @declare_command("name","")
943 def show(self, options, args):
945 show details about named registry record (Resolve)
951 # explicitly require Resolve to run in details mode
953 if not options.no_details: resolve_options['details']=True
954 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, resolve_options)
955 record_dicts = filter_records(options.type, record_dicts)
957 self.logger.error("No record of type %s"% options.type)
959 # user has required to focus on some keys
961 def project (record):
963 for key in options.keys:
964 try: projected[key]=record[key]
967 record_dicts = [ project (record) for record in record_dicts ]
968 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
969 for record in records:
970 if (options.format == "text"): record.dump(sort=True)
971 else: print record.save_as_xml()
973 save_records_to_file(options.file, record_dicts, options.fileformat)
976 # this historically was named 'add', it is now 'register' with an alias for legacy
977 @declare_command("[xml-filename]","",['add'])
978 def register(self, options, args):
979 """create new record in registry (Register)
980 from command line options (recommended)
981 old-school method involving an xml file still supported"""
983 auth_cred = self.my_authority_credential_string()
984 if options.show_credential:
985 show_credentials(auth_cred)
992 record_filepath = args[0]
993 rec_file = self.get_record_file(record_filepath)
994 record_dict.update(load_record_from_file(rec_file).todict())
996 print "Cannot load record file %s"%record_filepath
999 record_dict.update(load_record_from_opts(options).todict())
1000 # we should have a type by now
1001 if 'type' not in record_dict :
1004 # this is still planetlab dependent.. as plc will whine without that
1005 # also, it's only for adding
1006 if record_dict['type'] == 'user':
1007 if not 'first_name' in record_dict:
1008 record_dict['first_name'] = record_dict['hrn']
1009 if 'last_name' not in record_dict:
1010 record_dict['last_name'] = record_dict['hrn']
1011 return self.registry().Register(record_dict, auth_cred)
1013 @declare_command("[xml-filename]","")
1014 def update(self, options, args):
1015 """update record into registry (Update)
1016 from command line options (recommended)
1017 old-school method involving an xml file still supported"""
1020 record_filepath = args[0]
1021 rec_file = self.get_record_file(record_filepath)
1022 record_dict.update(load_record_from_file(rec_file).todict())
1024 record_dict.update(load_record_from_opts(options).todict())
1025 # at the very least we need 'type' here
1026 if 'type' not in record_dict:
1030 # don't translate into an object, as this would possibly distort
1031 # user-provided data; e.g. add an 'email' field to Users
1032 if record_dict['type'] == "user":
1033 if record_dict['hrn'] == self.user:
1034 cred = self.my_credential_string
1036 cred = self.my_authority_credential_string()
1037 elif record_dict['type'] in ["slice"]:
1039 cred = self.slice_credential_string(record_dict['hrn'])
1040 except ServerException, e:
1041 # XXX smbaker -- once we have better error return codes, update this
1042 # to do something better than a string compare
1043 if "Permission error" in e.args[0]:
1044 cred = self.my_authority_credential_string()
1047 elif record_dict['type'] in ["authority"]:
1048 cred = self.my_authority_credential_string()
1049 elif record_dict['type'] == 'node':
1050 cred = self.my_authority_credential_string()
1052 raise "unknown record type" + record_dict['type']
1053 if options.show_credential:
1054 show_credentials(cred)
1055 return self.registry().Update(record_dict, cred)
1057 @declare_command("hrn","")
1058 def remove(self, options, args):
1059 "remove registry record by name (Remove)"
1060 auth_cred = self.my_authority_credential_string()
1068 if options.show_credential:
1069 show_credentials(auth_cred)
1070 return self.registry().Remove(hrn, auth_cred, type)
1072 # ==================================================================
1073 # Slice-related commands
1074 # ==================================================================
1076 # show rspec for named slice
1077 @declare_command("","")
1078 def resources(self, options, args):
1080 discover available resources (ListResources)
1082 server = self.sliceapi()
1085 creds = [self.my_credential]
1086 if options.delegate:
1087 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1088 if options.show_credential:
1089 show_credentials(creds)
1091 # no need to check if server accepts the options argument since the options has
1092 # been a required argument since v1 API
1094 # always send call_id to v2 servers
1095 api_options ['call_id'] = unique_call_id()
1096 # ask for cached value if available
1097 api_options ['cached'] = True
1099 api_options['info'] = options.info
1100 if options.list_leases:
1101 api_options['list_leases'] = options.list_leases
1103 if options.current == True:
1104 api_options['cached'] = False
1106 api_options['cached'] = True
1107 if options.rspec_version:
1108 version_manager = VersionManager()
1109 server_version = self.get_cached_server_version(server)
1110 if 'sfa' in server_version:
1111 # just request the version the client wants
1112 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1114 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1116 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1117 result = server.ListResources (creds, api_options)
1118 value = ReturnValue.get_value(result)
1119 if self.options.raw:
1120 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1121 if options.file is not None:
1122 save_rspec_to_file(value, options.file)
1123 if (self.options.raw is None) and (options.file is None):
1124 display_rspec(value, options.format)
1128 @declare_command("slice_hrn","")
1129 def describe(self, options, args):
1131 shows currently allocated/provisioned resources
1132 of the named slice or set of slivers (Describe)
1134 server = self.sliceapi()
1137 creds = [self.slice_credential(args[0])]
1138 if options.delegate:
1139 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1140 if options.show_credential:
1141 show_credentials(creds)
1143 api_options = {'call_id': unique_call_id(),
1145 #'info': options.info,
1146 'list_leases': options.list_leases,
1147 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1150 api_options['info'] = options.info
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'}
1160 urn = Xrn(args[0], type='slice').get_urn()
1161 result = server.Describe([urn], creds, api_options)
1162 value = ReturnValue.get_value(result)
1163 if self.options.raw:
1164 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1165 if options.file is not None:
1166 save_rspec_to_file(value['geni_rspec'], options.file)
1167 if (self.options.raw is None) and (options.file is None):
1168 display_rspec(value, options.format)
1172 @declare_command("slice_hrn [<sliver_urn>...]","")
1173 def delete(self, options, args):
1175 de-allocate and de-provision all or named slivers of the named slice (Delete)
1177 server = self.sliceapi()
1181 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1184 # we have sliver urns
1185 sliver_urns = args[1:]
1187 # we provision all the slivers of the slice
1188 sliver_urns = [slice_urn]
1191 slice_cred = self.slice_credential(slice_hrn)
1192 creds = [slice_cred]
1194 # options and call_id when supported
1196 api_options ['call_id'] = unique_call_id()
1197 if options.show_credential:
1198 show_credentials(creds)
1199 result = server.Delete(sliver_urns, creds, *self.ois(server, api_options ) )
1200 value = ReturnValue.get_value(result)
1201 if self.options.raw:
1202 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1207 @declare_command("slice_hrn rspec","")
1208 def allocate(self, options, args):
1210 allocate resources to the named slice (Allocate)
1212 server = self.sliceapi()
1213 server_version = self.get_cached_server_version(server)
1215 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1218 creds = [self.slice_credential(slice_hrn)]
1220 delegated_cred = None
1221 if server_version.get('interface') == 'slicemgr':
1222 # delegate our cred to the slice manager
1223 # do not delegate cred to slicemgr...not working at the moment
1225 #if server_version.get('hrn'):
1226 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1227 #elif server_version.get('urn'):
1228 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1230 if options.show_credential:
1231 show_credentials(creds)
1234 rspec_file = self.get_rspec_file(args[1])
1235 rspec = open(rspec_file).read()
1237 api_options ['call_id'] = unique_call_id()
1241 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1242 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1243 slice_record = slice_records[0]
1244 user_hrns = slice_record['reg-researchers']
1245 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1246 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1247 sfa_users = sfa_users_arg(user_records, slice_record)
1248 geni_users = pg_users_arg(user_records)
1250 api_options['sfa_users'] = sfa_users
1251 api_options['geni_users'] = geni_users
1253 result = server.Allocate(slice_urn, creds, rspec, api_options)
1254 value = ReturnValue.get_value(result)
1255 if self.options.raw:
1256 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1257 if options.file is not None:
1258 save_rspec_to_file (value['geni_rspec'], options.file)
1259 if (self.options.raw is None) and (options.file is None):
1264 @declare_command("slice_hrn [<sliver_urn>...]","")
1265 def provision(self, options, args):
1267 provision all or named already allocated slivers of the named slice (Provision)
1269 server = self.sliceapi()
1270 server_version = self.get_cached_server_version(server)
1272 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1274 # we have sliver urns
1275 sliver_urns = args[1:]
1277 # we provision all the slivers of the slice
1278 sliver_urns = [slice_urn]
1281 creds = [self.slice_credential(slice_hrn)]
1282 delegated_cred = None
1283 if server_version.get('interface') == 'slicemgr':
1284 # delegate our cred to the slice manager
1285 # do not delegate cred to slicemgr...not working at the moment
1287 #if server_version.get('hrn'):
1288 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1289 #elif server_version.get('urn'):
1290 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1292 if options.show_credential:
1293 show_credentials(creds)
1296 api_options ['call_id'] = unique_call_id()
1298 # set the requtested rspec version
1299 version_manager = VersionManager()
1300 rspec_version = version_manager._get_version('geni', '3').to_dict()
1301 api_options['geni_rspec_version'] = rspec_version
1304 # need to pass along user keys to the aggregate.
1306 # { urn: urn:publicid:IDN+emulab.net+user+alice
1307 # keys: [<ssh key A>, <ssh key B>]
1310 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1311 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1312 slice_record = slice_records[0]
1313 user_hrns = slice_record['reg-researchers']
1314 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1315 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1316 users = pg_users_arg(user_records)
1318 api_options['geni_users'] = users
1319 result = server.Provision(sliver_urns, creds, api_options)
1320 value = ReturnValue.get_value(result)
1321 if self.options.raw:
1322 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1323 if options.file is not None:
1324 save_rspec_to_file (value['geni_rspec'], options.file)
1325 if (self.options.raw is None) and (options.file is None):
1329 @declare_command("slice_hrn","")
1330 def status(self, options, args):
1332 retrieve the status of the slivers belonging to the named slice (Status)
1334 server = self.sliceapi()
1338 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1341 slice_cred = self.slice_credential(slice_hrn)
1342 creds = [slice_cred]
1344 # options and call_id when supported
1346 api_options['call_id']=unique_call_id()
1347 if options.show_credential:
1348 show_credentials(creds)
1349 result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1350 value = ReturnValue.get_value(result)
1351 if self.options.raw:
1352 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1355 # Thierry: seemed to be missing
1358 @declare_command("slice_hrn [<sliver_urn>...] action","")
1359 def action(self, options, args):
1361 Perform the named operational action on all or named slivers of the named slice
1363 server = self.sliceapi()
1367 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1369 # we have sliver urns
1370 sliver_urns = args[1:-1]
1372 # we provision all the slivers of the slice
1373 sliver_urns = [slice_urn]
1376 slice_cred = self.slice_credential(args[0])
1377 creds = [slice_cred]
1378 if options.delegate:
1379 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1380 creds.append(delegated_cred)
1382 result = server.PerformOperationalAction(sliver_urns, creds, action , api_options)
1383 value = ReturnValue.get_value(result)
1384 if self.options.raw:
1385 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1390 @declare_command("slice_hrn [<sliver_urn>...] time","")
1391 def renew(self, options, args):
1395 server = self.sliceapi()
1400 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1403 # we have sliver urns
1404 sliver_urns = args[1:-1]
1406 # we provision all the slivers of the slice
1407 sliver_urns = [slice_urn]
1408 input_time = args[-1]
1410 # time: don't try to be smart on the time format, server-side will
1412 slice_cred = self.slice_credential(args[0])
1413 creds = [slice_cred]
1414 # options and call_id when supported
1416 api_options['call_id']=unique_call_id()
1417 if options.show_credential:
1418 show_credentials(creds)
1419 result = server.Renew(sliver_urns, creds, input_time, *self.ois(server,api_options))
1420 value = ReturnValue.get_value(result)
1421 if self.options.raw:
1422 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1428 @declare_command("slice_hrn","")
1429 def shutdown(self, options, args):
1431 shutdown named slice (Shutdown)
1433 server = self.sliceapi()
1436 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1438 slice_cred = self.slice_credential(slice_hrn)
1439 creds = [slice_cred]
1440 result = server.Shutdown(slice_urn, creds)
1441 value = ReturnValue.get_value(result)
1442 if self.options.raw:
1443 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1449 @declare_command("[name]","")
1450 def gid(self, options, args):
1452 Create a GID (CreateGid)
1457 target_hrn = args[0]
1458 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1459 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1461 filename = options.file
1463 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1464 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1465 GID(string=gid).save_to_file(filename)
1467 ####################
1468 @declare_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1470 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1471 the set of credentials in the scope for this call would be
1472 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1474 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1476 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1477 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1478 because of the two -s options
1481 def delegate (self, options, args):
1483 (locally) create delegate credential for use by given hrn
1484 make sure to check for 'sfi myslice' instead if you plan
1491 # support for several delegations in the same call
1492 # so first we gather the things to do
1494 for slice_hrn in options.delegate_slices:
1495 message="%s.slice"%slice_hrn
1496 original = self.slice_credential_string(slice_hrn)
1497 tuples.append ( (message, original,) )
1498 if options.delegate_pi:
1499 my_authority=self.authority
1500 message="%s.pi"%my_authority
1501 original = self.my_authority_credential_string()
1502 tuples.append ( (message, original,) )
1503 for auth_hrn in options.delegate_auths:
1504 message="%s.auth"%auth_hrn
1505 original=self.authority_credential_string(auth_hrn)
1506 tuples.append ( (message, original, ) )
1507 # if nothing was specified at all at this point, let's assume -u
1508 if not tuples: options.delegate_user=True
1510 if options.delegate_user:
1511 message="%s.user"%self.user
1512 original = self.my_credential_string
1513 tuples.append ( (message, original, ) )
1515 # default type for beneficial is user unless -A
1516 if options.delegate_to_authority: to_type='authority'
1517 else: to_type='user'
1519 # let's now handle all this
1520 # it's all in the filenaming scheme
1521 for (message,original) in tuples:
1522 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1523 delegated_credential = Credential (string=delegated_string)
1524 filename = os.path.join ( self.options.sfi_dir,
1525 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1526 delegated_credential.save_to_file(filename, save_parents=True)
1527 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1529 ####################
1530 @declare_command("","""$ less +/myslice sfi_config
1532 backend = http://manifold.pl.sophia.inria.fr:7080
1533 # the HRN that myslice uses, so that we are delegating to
1534 delegate = ple.upmc.slicebrowser
1535 # platform - this is a myslice concept
1537 # username - as of this writing (May 2013) a simple login name
1541 will first collect the slices that you are part of, then make sure
1542 all your credentials are up-to-date (read: refresh expired ones)
1543 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1544 and upload them all on myslice backend, using 'platform' and 'user'.
1545 A password will be prompted for the upload part.
1547 $ sfi -v myslice -- or sfi -vv myslice
1548 same but with more and more verbosity
1550 $ sfi m -b http://mymanifold.foo.com:7080/
1551 is synonym to sfi myslice as no other command starts with an 'm'
1552 and uses a custom backend for this one call
1555 def myslice (self, options, args):
1557 """ This helper is for refreshing your credentials at myslice; it will
1558 * compute all the slices that you currently have credentials on
1559 * refresh all your credentials (you as a user and pi, your slices)
1560 * upload them to the manifold backend server
1561 for last phase, sfi_config is read to look for the [myslice] section,
1562 and namely the 'backend', 'delegate' and 'user' settings"""
1568 # enable info by default
1569 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1570 ### the rough sketch goes like this
1571 # (0) produce a p12 file
1572 self.client_bootstrap.my_pkcs12()
1574 # (a) rain check for sufficient config in sfi_config
1576 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1577 for key in myslice_keys:
1579 # oct 2013 - I'm finding myself juggling with config files
1580 # so a couple of command-line options can now override config
1581 if hasattr(options,key) and getattr(options,key) is not None:
1582 value=getattr(options,key)
1584 full_key="MYSLICE_" + key.upper()
1585 value=getattr(self.config_instance,full_key,None)
1586 if value: myslice_dict[key]=value
1587 else: print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1588 if len(myslice_dict) != len(myslice_keys):
1591 # (b) figure whether we are PI for the authority where we belong
1592 self.logger.info("Resolving our own id %s"%self.user)
1593 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1594 if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1595 my_record=my_records[0]
1596 my_auths_all = my_record['reg-pi-authorities']
1597 self.logger.info("Found %d authorities that we are PI for"%len(my_auths_all))
1598 self.logger.debug("They are %s"%(my_auths_all))
1600 my_auths = my_auths_all
1601 if options.delegate_auths:
1602 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1603 self.logger.debug("Restricted to user-provided auths"%(my_auths))
1605 # (c) get the set of slices that we are in
1606 my_slices_all=my_record['reg-slices']
1607 self.logger.info("Found %d slices that we are member of"%len(my_slices_all))
1608 self.logger.debug("They are: %s"%(my_slices_all))
1610 my_slices = my_slices_all
1611 # if user provided slices, deal only with these - if they are found
1612 if options.delegate_slices:
1613 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1614 self.logger.debug("Restricted to user-provided slices: %s"%(my_slices))
1616 # (d) make sure we have *valid* credentials for all these
1618 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1619 for auth_hrn in my_auths:
1620 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1621 for slice_hrn in my_slices:
1622 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1624 # (e) check for the delegated version of these
1625 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1626 # switch to myslice using an authority instead of a user
1627 delegatee_type='user'
1628 delegatee_hrn=myslice_dict['delegate']
1629 hrn_delegated_credentials = []
1630 for (hrn, htype, credential) in hrn_credentials:
1631 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1632 # save these so user can monitor what she's uploaded
1633 filename = os.path.join ( self.options.sfi_dir,
1634 "%s.%s_for_%s.%s.cred"%(hrn,htype,delegatee_hrn,delegatee_type))
1635 with file(filename,'w') as f:
1636 f.write(delegated_credential)
1637 self.logger.debug("(Over)wrote %s"%filename)
1638 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1640 # (f) and finally upload them to manifold server
1641 # xxx todo add an option so the password can be set on the command line
1642 # (but *NOT* in the config file) so other apps can leverage this
1643 self.logger.info("Uploading on backend at %s"%myslice_dict['backend'])
1644 uploader = ManifoldUploader (logger=self.logger,
1645 url=myslice_dict['backend'],
1646 platform=myslice_dict['platform'],
1647 username=myslice_dict['username'],
1648 password=options.password)
1649 uploader.prompt_all()
1650 (count_all,count_success)=(0,0)
1651 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1653 inspect=Credential(string=delegated_credential)
1654 expire_datetime=inspect.get_expiration()
1655 message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
1656 if uploader.upload(delegated_credential,message=message):
1659 self.logger.info("Successfully uploaded %d/%d credentials"%(count_success,count_all))
1661 # at first I thought we would want to save these,
1662 # like 'sfi delegate does' but on second thought
1663 # it is probably not helpful as people would not
1664 # need to run 'sfi delegate' at all anymore
1665 if count_success != count_all: sys.exit(1)
1668 @declare_command("cred","")
1669 def trusted(self, options, args):
1671 return the trusted certs at this interface (get_trusted_certs)
1673 if options.registry_interface:
1674 server=self.registry()
1676 server = self.sliceapi()
1677 cred = self.my_authority_credential_string()
1678 trusted_certs = server.get_trusted_certs(cred)
1679 if not options.registry_interface:
1680 trusted_certs = ReturnValue.get_value(trusted_certs)
1682 for trusted_cert in trusted_certs:
1683 print "\n===========================================================\n"
1684 gid = GID(string=trusted_cert)
1686 cert = Certificate(string=trusted_cert)
1687 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1688 print "Certificate:\n%s\n\n"%trusted_cert