2 # sfi.py - basic SFA command-line client
3 # this module is also used in sfascan
18 from lxml import etree
19 from StringIO import StringIO
20 from optparse import OptionParser
21 from pprint import PrettyPrinter
22 from tempfile import mkstemp
24 from sfa.trust.certificate import Keypair, Certificate
25 from sfa.trust.gid import GID
26 from sfa.trust.credential import Credential
27 from sfa.trust.sfaticket import SfaTicket
29 from sfa.util.faults import SfaInvalidArgument
30 from sfa.util.sfalogging import sfi_logger
31 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
32 from sfa.util.config import Config
33 from sfa.util.version import version_core
34 from sfa.util.cache import Cache
36 from sfa.storage.record import Record
38 from sfa.rspecs.rspec import RSpec
39 from sfa.rspecs.rspec_converter import RSpecConverter
40 from sfa.rspecs.version_manager import VersionManager
42 from sfa.client.sfaclientlib import SfaClientBootstrap
43 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
44 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
45 from sfa.client.return_value import ReturnValue
46 from sfa.client.candidates import Candidates
47 from sfa.client.manifolduploader import ManifoldUploader
51 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
52 terminal_render, filter_records
55 def display_rspec(rspec, format='rspec'):
57 tree = etree.parse(StringIO(rspec))
59 result = root.xpath("./network/site/node/hostname/text()")
60 elif format in ['ip']:
61 # The IP address is not yet part of the new RSpec
62 # so this doesn't do anything yet.
63 tree = etree.parse(StringIO(rspec))
65 result = root.xpath("./network/site/node/ipv4/text()")
72 def display_list(results):
73 for result in results:
76 def display_records(recordList, dump=False):
77 ''' Print all fields in the record'''
78 for record in recordList:
79 display_record(record, dump)
81 def display_record(record, dump=False):
83 record.dump(sort=True)
85 info = record.getdict()
86 print "%s (%s)" % (info['hrn'], info['type'])
90 def filter_records(type, records):
92 for record in records:
93 if (record['type'] == type) or (type == "all"):
94 filtered_records.append(record)
95 return filtered_records
98 def credential_printable (cred):
99 credential=Credential(cred=cred)
101 result += credential.get_summary_tostring()
103 rights = credential.get_privileges()
104 result += "type=%s\n" % credential.type
105 result += "version=%s\n" % credential.version
106 result += "rights=%s\n"%rights
109 def show_credentials (cred_s):
110 if not isinstance (cred_s,list): cred_s = [cred_s]
112 print "Using Credential %s"%credential_printable(cred)
115 def save_raw_to_file(var, filename, format="text", banner=None):
117 # if filename is "-", send it to stdout
120 f = open(filename, "w")
125 elif format == "pickled":
126 f.write(pickle.dumps(var))
127 elif format == "json":
128 if hasattr(json, "dumps"):
129 f.write(json.dumps(var)) # python 2.6
131 f.write(json.write(var)) # python 2.5
133 # this should never happen
134 print "unknown output format", format
136 f.write('\n'+banner+"\n")
138 def save_rspec_to_file(rspec, filename):
139 if not filename.endswith(".rspec"):
140 filename = filename + ".rspec"
141 f = open(filename, 'w')
146 def save_records_to_file(filename, record_dicts, format="xml"):
149 for record_dict in record_dicts:
151 save_record_to_file(filename + "." + str(index), record_dict)
153 save_record_to_file(filename, record_dict)
155 elif format == "xmllist":
156 f = open(filename, "w")
157 f.write("<recordlist>\n")
158 for record_dict in record_dicts:
159 record_obj=Record(dict=record_dict)
160 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
161 f.write("</recordlist>\n")
163 elif format == "hrnlist":
164 f = open(filename, "w")
165 for record_dict in record_dicts:
166 record_obj=Record(dict=record_dict)
167 f.write(record_obj.hrn + "\n")
170 # this should never happen
171 print "unknown output format", format
173 def save_record_to_file(filename, record_dict):
174 record = Record(dict=record_dict)
175 xml = record.save_as_xml()
176 f=codecs.open(filename, encoding='utf-8',mode="w")
181 # minimally check a key argument
182 def check_ssh_key (key):
183 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
184 return re.match(good_ssh_key, key, re.IGNORECASE)
187 def load_record_from_opts(options):
189 if hasattr(options, 'xrn') and options.xrn:
190 if hasattr(options, 'type') and options.type:
191 xrn = Xrn(options.xrn, options.type)
193 xrn = Xrn(options.xrn)
194 record_dict['urn'] = xrn.get_urn()
195 record_dict['hrn'] = xrn.get_hrn()
196 record_dict['type'] = xrn.get_type()
197 if hasattr(options, 'key') and options.key:
199 pubkey = open(options.key, 'r').read()
202 if not check_ssh_key (pubkey):
203 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
204 record_dict['keys'] = [pubkey]
205 if hasattr(options, 'slices') and options.slices:
206 record_dict['slices'] = options.slices
207 if hasattr(options, 'researchers') and options.researchers:
208 record_dict['researcher'] = options.researchers
209 if hasattr(options, 'email') and options.email:
210 record_dict['email'] = options.email
211 if hasattr(options, 'pis') and options.pis:
212 record_dict['pi'] = options.pis
214 # handle extra settings
215 record_dict.update(options.extras)
217 return Record(dict=record_dict)
219 def load_record_from_file(filename):
220 f=codecs.open(filename, encoding="utf-8", mode="r")
221 xml_string = f.read()
223 return Record(xml=xml_string)
227 def unique_call_id(): return uuid.uuid4().urn
229 ########## a simple model for maintaing 3 doc attributes per command (instead of just one)
230 # essentially for the methods that implement a subcommand like sfi list
231 # we need to keep track of
232 # (*) doc a few lines that tell what it does, still located in __doc__
233 # (*) args_string a simple one-liner that describes mandatory arguments
234 # (*) example well, one or several releant examples
236 # since __doc__ only accounts for one, we use this simple mechanism below
237 # however we keep doc in place for easier migration
239 from functools import wraps
241 # we use a list as well as a dict so we can keep track of the order
245 def register_command (args_string, example):
247 name=getattr(m,'__name__')
248 doc=getattr(m,'__doc__',"-- missing doc --")
249 doc=doc.strip(" \t\n")
250 commands_list.append(name)
251 commands_dict[name]=(doc, args_string, example)
253 def new_method (*args, **kwds): return m(*args, **kwds)
261 # dirty hack to make this class usable from the outside
262 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
265 def default_sfi_dir ():
266 if os.path.isfile("./sfi_config"):
269 return os.path.expanduser("~/.sfi/")
271 # dummy to meet Sfi's expectations for its 'options' field
272 # i.e. s/t we can do setattr on
276 def __init__ (self,options=None):
277 if options is None: options=Sfi.DummyOptions()
278 for opt in Sfi.required_options:
279 if not hasattr(options,opt): setattr(options,opt,None)
280 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
281 self.options = options
283 self.authority = None
284 self.logger = sfi_logger
285 self.logger.enable_console()
286 ### various auxiliary material that we keep at hand
288 # need to call this other than just 'config' as we have a command/method with that name
289 self.config_instance=None
290 self.config_file=None
291 self.client_bootstrap=None
293 ### suitable if no reasonable command has been provided
294 def print_commands_help (self, options):
295 verbose=getattr(options,'verbose')
296 format3="%18s %-15s %s"
299 print format3%("command","cmd_args","description")
303 self.create_parser_global().print_help()
304 # preserve order from the code
305 for command in commands_list:
306 (doc, args_string, example) = commands_dict[command]
309 doc=doc.replace("\n","\n"+35*' ')
310 print format3%(command,args_string,doc)
312 self.create_parser_command(command).print_help()
314 ### now if a known command was found we can be more verbose on that one
315 def print_help (self):
316 print "==================== Generic sfi usage"
317 self.sfi_parser.print_help()
318 (doc,_,example)=commands_dict[self.command]
319 print "\n==================== Purpose of %s"%self.command
321 print "\n==================== Specific usage for %s"%self.command
322 self.command_parser.print_help()
324 print "\n==================== %s example(s)"%self.command
327 def create_parser_global(self):
328 # Generate command line parser
329 parser = OptionParser(add_help_option=False,
330 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
331 description="Commands: %s"%(" ".join(commands_list)))
332 parser.add_option("-r", "--registry", dest="registry",
333 help="root registry", metavar="URL", default=None)
334 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
335 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
336 parser.add_option("-R", "--raw", dest="raw", default=None,
337 help="Save raw, unparsed server response to a file")
338 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
339 help="raw file format ([text]|pickled|json)", default="text",
340 choices=("text","pickled","json"))
341 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
342 help="text string to write before and after raw output")
343 parser.add_option("-d", "--dir", dest="sfi_dir",
344 help="config & working directory - default is %default",
345 metavar="PATH", default=Sfi.default_sfi_dir())
346 parser.add_option("-u", "--user", dest="user",
347 help="user name", metavar="HRN", default=None)
348 parser.add_option("-a", "--auth", dest="auth",
349 help="authority name", metavar="HRN", default=None)
350 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
351 help="verbose mode - cumulative")
352 parser.add_option("-D", "--debug",
353 action="store_true", dest="debug", default=False,
354 help="Debug (xml-rpc) protocol messages")
355 # would it make sense to use ~/.ssh/id_rsa as a default here ?
356 parser.add_option("-k", "--private-key",
357 action="store", dest="user_private_key", default=None,
358 help="point to the private key file to use if not yet installed in sfi_dir")
359 parser.add_option("-t", "--timeout", dest="timeout", default=None,
360 help="Amout of time to wait before timing out the request")
361 parser.add_option("-h", "--help",
362 action="store_true", dest="help", default=False,
363 help="one page summary on commands & exit")
364 parser.disable_interspersed_args()
369 def create_parser_command(self, command):
370 if command not in commands_dict:
371 msg="Invalid command\n"
373 msg += ','.join(commands_list)
374 self.logger.critical(msg)
377 # retrieve args_string
378 (_, args_string, __) = commands_dict[command]
380 parser = OptionParser(add_help_option=False,
381 usage="sfi [sfi_options] %s [cmd_options] %s"
382 % (command, args_string))
383 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
384 help="Summary of one command usage")
386 if command in ("config"):
387 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
388 help='how myslice config variables as well')
390 if command in ("version"):
391 parser.add_option("-R","--registry-version",
392 action="store_true", dest="version_registry", default=False,
393 help="probe registry version instead of sliceapi")
394 parser.add_option("-l","--local",
395 action="store_true", dest="version_local", default=False,
396 help="display version of the local client")
398 if command in ("add", "update"):
399 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
400 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
401 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
402 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
404 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
405 default='', type="str", action='callback', callback=optparse_listvalue_callback)
406 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
407 help='Set/replace slice researchers', default='', type="str", action='callback',
408 callback=optparse_listvalue_callback)
409 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
410 default='', type="str", action='callback', callback=optparse_listvalue_callback)
411 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
412 action="callback", callback=optparse_dictvalue_callback, nargs=1,
413 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
415 # user specifies remote aggregate/sm/component
416 if command in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
417 "action", "shutdown", "renew", "status"):
418 parser.add_option("-d", "--delegate", dest="delegate", default=None,
420 help="Include a credential delegated to the user's root"+\
421 "authority in set of credentials for this call")
423 # show_credential option
424 if command in ("list","resources", "describe", "provision", "allocate", "add","update","remove","delete","status","renew"):
425 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
426 help="show credential(s) used in human-readable form")
427 # registy filter option
428 if command in ("list", "show", "remove"):
429 parser.add_option("-t", "--type", dest="type", type="choice",
430 help="type filter ([all]|user|slice|authority|node|aggregate)",
431 choices=("all", "user", "slice", "authority", "node", "aggregate"),
433 if command in ("show"):
434 parser.add_option("-k","--key",dest="keys",action="append",default=[],
435 help="specify specific keys to be displayed from record")
436 if command in ("resources", "describe"):
438 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
439 help="schema type and version of resulting RSpec")
440 # disable/enable cached rspecs
441 parser.add_option("-c", "--current", dest="current", default=False,
443 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
445 parser.add_option("-f", "--format", dest="format", type="choice",
446 help="display format ([xml]|dns|ip)", default="xml",
447 choices=("xml", "dns", "ip"))
448 #panos: a new option to define the type of information about resources a user is interested in
449 parser.add_option("-i", "--info", dest="info",
450 help="optional component information", default=None)
451 # a new option to retreive or not reservation-oriented RSpecs (leases)
452 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
453 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
454 choices=("all", "resources", "leases"), default="resources")
457 if command in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
458 parser.add_option("-o", "--output", dest="file",
459 help="output XML to file", metavar="FILE", default=None)
461 if command in ("show", "list"):
462 parser.add_option("-f", "--format", dest="format", type="choice",
463 help="display format ([text]|xml)", default="text",
464 choices=("text", "xml"))
466 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
467 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
468 choices=("xml", "xmllist", "hrnlist"))
469 if command == 'list':
470 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
471 help="list all child records", default=False)
472 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
473 help="gives details, like user keys", default=False)
474 if command in ("delegate"):
475 parser.add_option("-u", "--user",
476 action="store_true", dest="delegate_user", default=False,
477 help="delegate your own credentials; default if no other option is provided")
478 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
479 metavar="slice_hrn", help="delegate cred. for slice HRN")
480 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
481 metavar='auth_hrn', help="delegate cred for auth HRN")
482 # this primarily is a shorthand for -a my_hrn
483 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
484 help="delegate your PI credentials, so s.t. like -a your_hrn^")
485 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
486 help="""by default the mandatory argument is expected to be a user,
487 use this if you mean an authority instead""")
489 if command in ("myslice"):
490 parser.add_option("-p","--password",dest='password',action='store',default=None,
491 help="specify mainfold password on the command line")
492 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
493 metavar="slice_hrn", help="delegate cred. for slice HRN")
494 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
495 metavar='auth_hrn', help="delegate PI cred for auth HRN")
496 parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
497 parser.add_option('-b', '--backend', dest='backend', help="Override 'backend' from the config file")
503 # Main: parse arguments and dispatch to command
505 def dispatch(self, command, command_options, command_args):
506 method=getattr(self, command, None)
508 print "Unknown command %s"%command
510 return method(command_options, command_args)
513 self.sfi_parser = self.create_parser_global()
514 (options, args) = self.sfi_parser.parse_args()
516 self.print_commands_help(options)
518 self.options = options
520 self.logger.setLevelFromOptVerbose(self.options.verbose)
523 self.logger.critical("No command given. Use -h for help.")
524 self.print_commands_help(options)
527 # complete / find unique match with command set
528 command_candidates = Candidates (commands_list)
530 command = command_candidates.only_match(input)
532 self.print_commands_help(options)
534 # second pass options parsing
536 self.command_parser = self.create_parser_command(command)
537 (command_options, command_args) = self.command_parser.parse_args(args[1:])
538 if command_options.help:
541 self.command_options = command_options
545 self.logger.debug("Command=%s" % self.command)
548 self.dispatch(command, command_options, command_args)
552 self.logger.log_exc ("sfi command %s failed"%command)
558 def read_config(self):
559 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
560 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
562 if Config.is_ini(config_file):
563 config = Config (config_file)
565 # try upgrading from shell config format
566 fp, fn = mkstemp(suffix='sfi_config', text=True)
568 # we need to preload the sections we want parsed
569 # from the shell config
570 config.add_section('sfi')
571 # sface users should be able to use this same file to configure their stuff
572 config.add_section('sface')
573 # manifold users should be able to specify the details
574 # of their backend server here for 'sfi myslice'
575 config.add_section('myslice')
576 config.load(config_file)
578 shutil.move(config_file, shell_config_file)
580 config.save(config_file)
583 self.logger.critical("Failed to read configuration file %s"%config_file)
584 self.logger.info("Make sure to remove the export clauses and to add quotes")
585 if self.options.verbose==0:
586 self.logger.info("Re-run with -v for more details")
588 self.logger.log_exc("Could not read config file %s"%config_file)
591 self.config_instance=config
594 if (self.options.sm is not None):
595 self.sm_url = self.options.sm
596 elif hasattr(config, "SFI_SM"):
597 self.sm_url = config.SFI_SM
599 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
603 if (self.options.registry is not None):
604 self.reg_url = self.options.registry
605 elif hasattr(config, "SFI_REGISTRY"):
606 self.reg_url = config.SFI_REGISTRY
608 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
612 if (self.options.user is not None):
613 self.user = self.options.user
614 elif hasattr(config, "SFI_USER"):
615 self.user = config.SFI_USER
617 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
621 if (self.options.auth is not None):
622 self.authority = self.options.auth
623 elif hasattr(config, "SFI_AUTH"):
624 self.authority = config.SFI_AUTH
626 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
629 self.config_file=config_file
634 # Get various credential and spec files
636 # Establishes limiting conventions
637 # - conflates MAs and SAs
638 # - assumes last token in slice name is unique
640 # Bootstraps credentials
641 # - bootstrap user credential from self-signed certificate
642 # - bootstrap authority credential from user credential
643 # - bootstrap slice credential from user credential
646 # init self-signed cert, user credentials and gid
647 def bootstrap (self):
648 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
650 # if -k is provided, use this to initialize private key
651 if self.options.user_private_key:
652 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
654 # trigger legacy compat code if needed
655 # the name has changed from just <leaf>.pkey to <hrn>.pkey
656 if not os.path.isfile(client_bootstrap.private_key_filename()):
657 self.logger.info ("private key not found, trying legacy name")
659 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
660 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
661 client_bootstrap.init_private_key_if_missing (legacy_private_key)
662 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
664 self.logger.log_exc("Can't find private key ")
668 client_bootstrap.bootstrap_my_gid()
669 # extract what's needed
670 self.private_key = client_bootstrap.private_key()
671 self.my_credential_string = client_bootstrap.my_credential_string ()
672 self.my_credential = {'geni_type': 'geni_sfa',
674 'geni_value': self.my_credential_string}
675 self.my_gid = client_bootstrap.my_gid ()
676 self.client_bootstrap = client_bootstrap
679 def my_authority_credential_string(self):
680 if not self.authority:
681 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
683 return self.client_bootstrap.authority_credential_string (self.authority)
685 def authority_credential_string(self, auth_hrn):
686 return self.client_bootstrap.authority_credential_string (auth_hrn)
688 def slice_credential_string(self, name):
689 return self.client_bootstrap.slice_credential_string (name)
691 def slice_credential(self, name):
692 return {'geni_type': 'geni_sfa',
694 'geni_value': self.slice_credential_string(name)}
696 # xxx should be supported by sfaclientbootstrap as well
697 def delegate_cred(self, object_cred, hrn, type='authority'):
698 # the gid and hrn of the object we are delegating
699 if isinstance(object_cred, str):
700 object_cred = Credential(string=object_cred)
701 object_gid = object_cred.get_gid_object()
702 object_hrn = object_gid.get_hrn()
704 if not object_cred.get_privileges().get_all_delegate():
705 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
708 # the delegating user's gid
709 caller_gidfile = self.my_gid()
711 # the gid of the user who will be delegated to
712 delegee_gid = self.client_bootstrap.gid(hrn,type)
713 delegee_hrn = delegee_gid.get_hrn()
714 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
715 return dcred.save_to_string(save_parents=True)
718 # Management of the servers
723 if not hasattr (self, 'registry_proxy'):
724 self.logger.info("Contacting Registry at: %s"%self.reg_url)
725 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
726 timeout=self.options.timeout, verbose=self.options.debug)
727 return self.registry_proxy
731 if not hasattr (self, 'sliceapi_proxy'):
732 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
733 if hasattr(self.command_options,'component') and self.command_options.component:
734 # resolve the hrn at the registry
735 node_hrn = self.command_options.component
736 records = self.registry().Resolve(node_hrn, self.my_credential_string)
737 records = filter_records('node', records)
739 self.logger.warning("No such component:%r"% opts.component)
741 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
742 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
744 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
745 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
746 self.sm_url = 'http://' + self.sm_url
747 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
748 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
749 timeout=self.options.timeout, verbose=self.options.debug)
750 return self.sliceapi_proxy
752 def get_cached_server_version(self, server):
753 # check local cache first
756 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
757 cache_key = server.url + "-version"
759 cache = Cache(cache_file)
762 self.logger.info("Local cache not found at: %s" % cache_file)
765 version = cache.get(cache_key)
768 result = server.GetVersion()
769 version= ReturnValue.get_value(result)
770 # cache version for 20 minutes
771 cache.add(cache_key, version, ttl= 60*20)
772 self.logger.info("Updating cache file %s" % cache_file)
773 cache.save_to_file(cache_file)
777 ### resurrect this temporarily so we can support V1 aggregates for a while
778 def server_supports_options_arg(self, server):
780 Returns true if server support the optional call_id arg, false otherwise.
782 server_version = self.get_cached_server_version(server)
784 # xxx need to rewrite this
785 if int(server_version.get('geni_api')) >= 2:
789 def server_supports_call_id_arg(self, server):
790 server_version = self.get_cached_server_version(server)
792 if 'sfa' in server_version and 'code_tag' in server_version:
793 code_tag = server_version['code_tag']
794 code_tag_parts = code_tag.split("-")
795 version_parts = code_tag_parts[0].split(".")
796 major, minor = version_parts[0], version_parts[1]
797 rev = code_tag_parts[1]
798 if int(major) == 1 and minor == 0 and build >= 22:
802 ### ois = options if supported
803 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
804 def ois (self, server, option_dict):
805 if self.server_supports_options_arg (server):
807 elif self.server_supports_call_id_arg (server):
808 return [ unique_call_id () ]
812 ### cis = call_id if supported - like ois
813 def cis (self, server):
814 if self.server_supports_call_id_arg (server):
815 return [ unique_call_id ]
819 ######################################## miscell utilities
820 def get_rspec_file(self, rspec):
821 if (os.path.isabs(rspec)):
824 file = os.path.join(self.options.sfi_dir, rspec)
825 if (os.path.isfile(file)):
828 self.logger.critical("No such rspec file %s"%rspec)
831 def get_record_file(self, record):
832 if (os.path.isabs(record)):
835 file = os.path.join(self.options.sfi_dir, record)
836 if (os.path.isfile(file)):
839 self.logger.critical("No such registry record file %s"%record)
843 #==========================================================================
844 # Following functions implement the commands
846 # Registry-related commands
847 #==========================================================================
849 @register_command("","")
850 def config (self, options, args):
851 "Display contents of current config"
852 print "# From configuration file %s"%self.config_file
853 flags=[ ('sfi', [ ('registry','reg_url'),
854 ('auth','authority'),
860 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
862 for (section, tuples) in flags:
865 for (external_name, internal_name) in tuples:
866 print "%-20s = %s"%(external_name,getattr(self,internal_name))
869 varname="%s_%s"%(section.upper(),name.upper())
870 value=getattr(self.config_instance,varname)
871 print "%-20s = %s"%(name,value)
873 @register_command("","")
874 def version(self, options, args):
876 display an SFA server version (GetVersion)
877 or version information about sfi itself
879 if options.version_local:
880 version=version_core()
882 if options.version_registry:
883 server=self.registry()
885 server = self.sliceapi()
886 result = server.GetVersion()
887 version = ReturnValue.get_value(result)
889 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
891 pprinter = PrettyPrinter(indent=4)
892 pprinter.pprint(version)
894 @register_command("authority","")
895 def list(self, options, args):
897 list entries in named authority registry (List)
904 if options.recursive:
905 opts['recursive'] = options.recursive
907 if options.show_credential:
908 show_credentials(self.my_credential_string)
910 list = self.registry().List(hrn, self.my_credential_string, options)
912 raise Exception, "Not enough parameters for the 'list' command"
914 # filter on person, slice, site, node, etc.
915 # This really should be in the self.filter_records funct def comment...
916 list = filter_records(options.type, list)
917 terminal_render (list, options)
919 save_records_to_file(options.file, list, options.fileformat)
922 @register_command("name","")
923 def show(self, options, args):
925 show details about named registry record (Resolve)
931 # explicitly require Resolve to run in details mode
932 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
933 record_dicts = filter_records(options.type, record_dicts)
935 self.logger.error("No record of type %s"% options.type)
937 # user has required to focus on some keys
939 def project (record):
941 for key in options.keys:
942 try: projected[key]=record[key]
945 record_dicts = [ project (record) for record in record_dicts ]
946 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
947 for record in records:
948 if (options.format == "text"): record.dump(sort=True)
949 else: print record.save_as_xml()
951 save_records_to_file(options.file, record_dicts, options.fileformat)
954 @register_command("[xml-filename]","")
955 def add(self, options, args):
956 """add record into registry (Register)
957 from command line options (recommended)
958 old-school method involving an xml file still supported"""
960 auth_cred = self.my_authority_credential_string()
961 if options.show_credential:
962 show_credentials(auth_cred)
969 record_filepath = args[0]
970 rec_file = self.get_record_file(record_filepath)
971 record_dict.update(load_record_from_file(rec_file).todict())
973 print "Cannot load record file %s"%record_filepath
976 record_dict.update(load_record_from_opts(options).todict())
977 # we should have a type by now
978 if 'type' not in record_dict :
981 # this is still planetlab dependent.. as plc will whine without that
982 # also, it's only for adding
983 if record_dict['type'] == 'user':
984 if not 'first_name' in record_dict:
985 record_dict['first_name'] = record_dict['hrn']
986 if 'last_name' not in record_dict:
987 record_dict['last_name'] = record_dict['hrn']
988 return self.registry().Register(record_dict, auth_cred)
990 @register_command("[xml-filename]","")
991 def update(self, options, args):
992 """update record into registry (Update)
993 from command line options (recommended)
994 old-school method involving an xml file still supported"""
997 record_filepath = args[0]
998 rec_file = self.get_record_file(record_filepath)
999 record_dict.update(load_record_from_file(rec_file).todict())
1001 record_dict.update(load_record_from_opts(options).todict())
1002 # at the very least we need 'type' here
1003 if 'type' not in record_dict:
1007 # don't translate into an object, as this would possibly distort
1008 # user-provided data; e.g. add an 'email' field to Users
1009 if record_dict['type'] == "user":
1010 if record_dict['hrn'] == self.user:
1011 cred = self.my_credential_string
1013 cred = self.my_authority_credential_string()
1014 elif record_dict['type'] in ["slice"]:
1016 cred = self.slice_credential_string(record_dict['hrn'])
1017 except ServerException, e:
1018 # XXX smbaker -- once we have better error return codes, update this
1019 # to do something better than a string compare
1020 if "Permission error" in e.args[0]:
1021 cred = self.my_authority_credential_string()
1024 elif record_dict['type'] in ["authority"]:
1025 cred = self.my_authority_credential_string()
1026 elif record_dict['type'] == 'node':
1027 cred = self.my_authority_credential_string()
1029 raise "unknown record type" + record_dict['type']
1030 if options.show_credential:
1031 show_credentials(cred)
1032 return self.registry().Update(record_dict, cred)
1034 @register_command("hrn","")
1035 def remove(self, options, args):
1036 "remove registry record by name (Remove)"
1037 auth_cred = self.my_authority_credential_string()
1045 if options.show_credential:
1046 show_credentials(auth_cred)
1047 return self.registry().Remove(hrn, auth_cred, type)
1049 # ==================================================================
1050 # Slice-related commands
1051 # ==================================================================
1053 # show rspec for named slice
1054 @register_command("","")
1055 def resources(self, options, args):
1057 discover available resources (ListResources)
1059 server = self.sliceapi()
1062 creds = [self.my_credential]
1063 if options.delegate:
1064 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1065 if options.show_credential:
1066 show_credentials(creds)
1068 # no need to check if server accepts the options argument since the options has
1069 # been a required argument since v1 API
1071 # always send call_id to v2 servers
1072 api_options ['call_id'] = unique_call_id()
1073 # ask for cached value if available
1074 api_options ['cached'] = True
1076 api_options['info'] = options.info
1077 if options.list_leases:
1078 api_options['list_leases'] = options.list_leases
1080 if options.current == True:
1081 api_options['cached'] = False
1083 api_options['cached'] = True
1084 if options.rspec_version:
1085 version_manager = VersionManager()
1086 server_version = self.get_cached_server_version(server)
1087 if 'sfa' in server_version:
1088 # just request the version the client wants
1089 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1091 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1093 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1094 result = server.ListResources (creds, api_options)
1095 value = ReturnValue.get_value(result)
1096 if self.options.raw:
1097 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1098 if options.file is not None:
1099 save_rspec_to_file(value, options.file)
1100 if (self.options.raw is None) and (options.file is None):
1101 display_rspec(value, options.format)
1105 @register_command("slice_hrn","")
1106 def describe(self, options, args):
1108 shows currently allocated/provisioned resources
1109 of the named slice or set of slivers (Describe)
1111 server = self.sliceapi()
1114 creds = [self.slice_credential(args[0])]
1115 if options.delegate:
1116 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1117 if options.show_credential:
1118 show_credentials(creds)
1120 api_options = {'call_id': unique_call_id(),
1122 'info': options.info,
1123 'list_leases': options.list_leases,
1124 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1126 if options.rspec_version:
1127 version_manager = VersionManager()
1128 server_version = self.get_cached_server_version(server)
1129 if 'sfa' in server_version:
1130 # just request the version the client wants
1131 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1133 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1134 urn = Xrn(args[0], type='slice').get_urn()
1135 result = server.Describe([urn], creds, api_options)
1136 value = ReturnValue.get_value(result)
1137 if self.options.raw:
1138 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1139 if options.file is not None:
1140 save_rspec_to_file(value, options.file)
1141 if (self.options.raw is None) and (options.file is None):
1142 display_rspec(value, options.format)
1146 @register_command("slice_hrn","")
1147 def delete(self, options, args):
1149 de-allocate and de-provision all or named slivers of the slice (Delete)
1151 server = self.sliceapi()
1155 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1158 slice_cred = self.slice_credential(slice_hrn)
1159 creds = [slice_cred]
1161 # options and call_id when supported
1163 api_options ['call_id'] = unique_call_id()
1164 if options.show_credential:
1165 show_credentials(creds)
1166 result = server.Delete([slice_urn], creds, *self.ois(server, api_options ) )
1167 value = ReturnValue.get_value(result)
1168 if self.options.raw:
1169 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1174 @register_command("slice_hrn rspec","")
1175 def allocate(self, options, args):
1177 allocate resources to the named slice (Allocate)
1179 server = self.sliceapi()
1180 server_version = self.get_cached_server_version(server)
1182 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1185 creds = [self.slice_credential(slice_hrn)]
1187 delegated_cred = None
1188 if server_version.get('interface') == 'slicemgr':
1189 # delegate our cred to the slice manager
1190 # do not delegate cred to slicemgr...not working at the moment
1192 #if server_version.get('hrn'):
1193 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1194 #elif server_version.get('urn'):
1195 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1197 if options.show_credential:
1198 show_credentials(creds)
1201 rspec_file = self.get_rspec_file(args[1])
1202 rspec = open(rspec_file).read()
1204 api_options ['call_id'] = unique_call_id()
1208 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1209 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1210 slice_record = slice_records[0]
1211 user_hrns = slice_record['reg-researchers']
1212 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1213 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1214 sfa_users = sfa_users_arg(user_records, slice_record)
1215 geni_users = pg_users_arg(user_records)
1217 api_options['sfa_users'] = sfa_users
1218 api_options['geni_users'] = geni_users
1220 result = server.Allocate(slice_urn, creds, rspec, api_options)
1221 value = ReturnValue.get_value(result)
1222 if self.options.raw:
1223 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1224 if options.file is not None:
1225 save_rspec_to_file (value, options.file)
1226 if (self.options.raw is None) and (options.file is None):
1231 @register_command("slice_hrn","")
1232 def provision(self, options, args):
1234 provision already allocated resources of named slice (Provision)
1236 server = self.sliceapi()
1237 server_version = self.get_cached_server_version(server)
1239 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1242 creds = [self.slice_credential(slice_hrn)]
1243 delegated_cred = None
1244 if server_version.get('interface') == 'slicemgr':
1245 # delegate our cred to the slice manager
1246 # do not delegate cred to slicemgr...not working at the moment
1248 #if server_version.get('hrn'):
1249 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1250 #elif server_version.get('urn'):
1251 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1253 if options.show_credential:
1254 show_credentials(creds)
1257 api_options ['call_id'] = unique_call_id()
1259 # set the requtested rspec version
1260 version_manager = VersionManager()
1261 rspec_version = version_manager._get_version('geni', '3').to_dict()
1262 api_options['geni_rspec_version'] = rspec_version
1265 # need to pass along user keys to the aggregate.
1267 # { urn: urn:publicid:IDN+emulab.net+user+alice
1268 # keys: [<ssh key A>, <ssh key B>]
1271 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1272 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1273 slice_record = slice_records[0]
1274 user_hrns = slice_record['reg-researchers']
1275 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1276 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1277 users = pg_users_arg(user_records)
1279 api_options['geni_users'] = users
1280 result = server.Provision([slice_urn], creds, api_options)
1281 value = ReturnValue.get_value(result)
1282 if self.options.raw:
1283 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1284 if options.file is not None:
1285 save_rspec_to_file (value, options.file)
1286 if (self.options.raw is None) and (options.file is None):
1290 @register_command("slice_hrn","")
1291 def status(self, options, args):
1293 retrieve the status of the slivers belonging to tne named slice (Status)
1295 server = self.sliceapi()
1299 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1302 slice_cred = self.slice_credential(slice_hrn)
1303 creds = [slice_cred]
1305 # options and call_id when supported
1307 api_options['call_id']=unique_call_id()
1308 if options.show_credential:
1309 show_credentials(creds)
1310 result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1311 value = ReturnValue.get_value(result)
1312 if self.options.raw:
1313 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1316 # Thierry: seemed to be missing
1319 @register_command("slice_hrn action","")
1320 def action(self, options, args):
1322 Perform the named operational action on these slivers
1324 server = self.sliceapi()
1329 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1331 slice_cred = self.slice_credential(args[0])
1332 creds = [slice_cred]
1333 if options.delegate:
1334 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1335 creds.append(delegated_cred)
1337 result = server.PerformOperationalAction([slice_urn], creds, action , api_options)
1338 value = ReturnValue.get_value(result)
1339 if self.options.raw:
1340 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1345 @register_command("slice_hrn time","")
1346 def renew(self, options, args):
1348 renew slice (RenewSliver)
1350 server = self.sliceapi()
1354 [ slice_hrn, input_time ] = args
1356 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1357 # time: don't try to be smart on the time format, server-side will
1359 slice_cred = self.slice_credential(args[0])
1360 creds = [slice_cred]
1361 # options and call_id when supported
1363 api_options['call_id']=unique_call_id()
1364 if options.show_credential:
1365 show_credentials(creds)
1366 result = server.Renew([slice_urn], creds, input_time, *self.ois(server,api_options))
1367 value = ReturnValue.get_value(result)
1368 if self.options.raw:
1369 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1375 @register_command("slice_hrn","")
1376 def shutdown(self, options, args):
1378 shutdown named slice (Shutdown)
1380 server = self.sliceapi()
1383 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1385 slice_cred = self.slice_credential(slice_hrn)
1386 creds = [slice_cred]
1387 result = server.Shutdown(slice_urn, creds)
1388 value = ReturnValue.get_value(result)
1389 if self.options.raw:
1390 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1396 @register_command("[name]","")
1397 def gid(self, options, args):
1399 Create a GID (CreateGid)
1404 target_hrn = args[0]
1405 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1406 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1408 filename = options.file
1410 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1411 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1412 GID(string=gid).save_to_file(filename)
1414 ####################
1415 @register_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1417 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1418 the set of credentials in the scope for this call would be
1419 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1421 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1423 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1424 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1425 because of the two -s options
1428 def delegate (self, options, args):
1430 (locally) create delegate credential for use by given hrn
1431 make sure to check for 'sfi myslice' instead if you plan
1438 # support for several delegations in the same call
1439 # so first we gather the things to do
1441 for slice_hrn in options.delegate_slices:
1442 message="%s.slice"%slice_hrn
1443 original = self.slice_credential_string(slice_hrn)
1444 tuples.append ( (message, original,) )
1445 if options.delegate_pi:
1446 my_authority=self.authority
1447 message="%s.pi"%my_authority
1448 original = self.my_authority_credential_string()
1449 tuples.append ( (message, original,) )
1450 for auth_hrn in options.delegate_auths:
1451 message="%s.auth"%auth_hrn
1452 original=self.authority_credential_string(auth_hrn)
1453 tuples.append ( (message, original, ) )
1454 # if nothing was specified at all at this point, let's assume -u
1455 if not tuples: options.delegate_user=True
1457 if options.delegate_user:
1458 message="%s.user"%self.user
1459 original = self.my_credential_string
1460 tuples.append ( (message, original, ) )
1462 # default type for beneficial is user unless -A
1463 if options.delegate_to_authority: to_type='authority'
1464 else: to_type='user'
1466 # let's now handle all this
1467 # it's all in the filenaming scheme
1468 for (message,original) in tuples:
1469 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1470 delegated_credential = Credential (string=delegated_string)
1471 filename = os.path.join ( self.options.sfi_dir,
1472 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1473 delegated_credential.save_to_file(filename, save_parents=True)
1474 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1476 ####################
1477 @register_command("","""$ less +/myslice sfi_config
1479 backend = http://manifold.pl.sophia.inria.fr:7080
1480 # the HRN that myslice uses, so that we are delegating to
1481 delegate = ple.upmc.slicebrowser
1482 # platform - this is a myslice concept
1484 # username - as of this writing (May 2013) a simple login name
1488 will first collect the slices that you are part of, then make sure
1489 all your credentials are up-to-date (read: refresh expired ones)
1490 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1491 and upload them all on myslice backend, using 'platform' and 'user'.
1492 A password will be prompted for the upload part.
1494 $ sfi -v myslice -- or sfi -vv myslice
1495 same but with more and more verbosity
1497 $ sfi m -b http://mymanifold.foo.com:7080/
1498 is synonym to sfi myslice as no other command starts with an 'm'
1499 and uses a custom backend for this one call
1501 ) # register_command
1502 def myslice (self, options, args):
1504 """ This helper is for refreshing your credentials at myslice; it will
1505 * compute all the slices that you currently have credentials on
1506 * refresh all your credentials (you as a user and pi, your slices)
1507 * upload them to the manifold backend server
1508 for last phase, sfi_config is read to look for the [myslice] section,
1509 and namely the 'backend', 'delegate' and 'user' settings"""
1515 # enable info by default
1516 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1517 ### the rough sketch goes like this
1518 # (0) produce a p12 file
1519 self.client_bootstrap.my_pkcs12()
1521 # (a) rain check for sufficient config in sfi_config
1523 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1524 for key in myslice_keys:
1526 # oct 2013 - I'm finding myself juggling with config files
1527 # so a couple of command-line options can now override config
1528 if hasattr(options,key) and getattr(options,key) is not None:
1529 value=getattr(options,key)
1531 full_key="MYSLICE_" + key.upper()
1532 value=getattr(self.config_instance,full_key,None)
1533 if value: myslice_dict[key]=value
1534 else: print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1535 if len(myslice_dict) != len(myslice_keys):
1538 # (b) figure whether we are PI for the authority where we belong
1539 self.logger.info("Resolving our own id %s"%self.user)
1540 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1541 if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1542 my_record=my_records[0]
1543 my_auths_all = my_record['reg-pi-authorities']
1544 self.logger.info("Found %d authorities that we are PI for"%len(my_auths_all))
1545 self.logger.debug("They are %s"%(my_auths_all))
1547 my_auths = my_auths_all
1548 if options.delegate_auths:
1549 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1550 self.logger.debug("Restricted to user-provided auths"%(my_auths))
1552 # (c) get the set of slices that we are in
1553 my_slices_all=my_record['reg-slices']
1554 self.logger.info("Found %d slices that we are member of"%len(my_slices_all))
1555 self.logger.debug("They are: %s"%(my_slices_all))
1557 my_slices = my_slices_all
1558 # if user provided slices, deal only with these - if they are found
1559 if options.delegate_slices:
1560 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1561 self.logger.debug("Restricted to user-provided slices: %s"%(my_slices))
1563 # (d) make sure we have *valid* credentials for all these
1565 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1566 for auth_hrn in my_auths:
1567 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1568 for slice_hrn in my_slices:
1569 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1571 # (e) check for the delegated version of these
1572 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1573 # switch to myslice using an authority instead of a user
1574 delegatee_type='user'
1575 delegatee_hrn=myslice_dict['delegate']
1576 hrn_delegated_credentials = []
1577 for (hrn, htype, credential) in hrn_credentials:
1578 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1579 # save these so user can monitor what she's uploaded
1580 filename = os.path.join ( self.options.sfi_dir,
1581 "%s.%s_for_%s.%s.cred"%(hrn,htype,delegatee_hrn,delegatee_type))
1582 with file(filename,'w') as f:
1583 f.write(delegated_credential)
1584 self.logger.debug("(Over)wrote %s"%filename)
1585 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1587 # (f) and finally upload them to manifold server
1588 # xxx todo add an option so the password can be set on the command line
1589 # (but *NOT* in the config file) so other apps can leverage this
1590 self.logger.info("Uploading on backend at %s"%myslice_dict['backend'])
1591 uploader = ManifoldUploader (logger=self.logger,
1592 url=myslice_dict['backend'],
1593 platform=myslice_dict['platform'],
1594 username=myslice_dict['username'],
1595 password=options.password)
1596 uploader.prompt_all()
1597 (count_all,count_success)=(0,0)
1598 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1600 inspect=Credential(string=delegated_credential)
1601 expire_datetime=inspect.get_expiration()
1602 message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
1603 if uploader.upload(delegated_credential,message=message):
1606 self.logger.info("Successfully uploaded %d/%d credentials"%(count_success,count_all))
1608 # at first I thought we would want to save these,
1609 # like 'sfi delegate does' but on second thought
1610 # it is probably not helpful as people would not
1611 # need to run 'sfi delegate' at all anymore
1612 if count_success != count_all: sys.exit(1)
1615 @register_command("cred","")
1616 def trusted(self, options, args):
1618 return the trusted certs at this interface (get_trusted_certs)
1620 trusted_certs = self.registry().get_trusted_certs()
1621 for trusted_cert in trusted_certs:
1622 print "\n===========================================================\n"
1623 gid = GID(string=trusted_cert)
1625 cert = Certificate(string=trusted_cert)
1626 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1627 print "Certificate:\n%s\n\n"%trusted_cert