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:
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 register_command (args_string, example):
246 name=getattr(m,'__name__')
247 doc=getattr(m,'__doc__',"-- missing doc --")
248 doc=doc.strip(" \t\n")
249 commands_list.append(name)
250 commands_dict[name]=(doc, args_string, example)
252 def new_method (*args, **kwds): return m(*args, **kwds)
260 # dirty hack to make this class usable from the outside
261 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
264 def default_sfi_dir ():
265 if os.path.isfile("./sfi_config"):
268 return os.path.expanduser("~/.sfi/")
270 # dummy to meet Sfi's expectations for its 'options' field
271 # i.e. s/t we can do setattr on
275 def __init__ (self,options=None):
276 if options is None: options=Sfi.DummyOptions()
277 for opt in Sfi.required_options:
278 if not hasattr(options,opt): setattr(options,opt,None)
279 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
280 self.options = options
282 self.authority = None
283 self.logger = sfi_logger
284 self.logger.enable_console()
285 ### various auxiliary material that we keep at hand
287 # need to call this other than just 'config' as we have a command/method with that name
288 self.config_instance=None
289 self.config_file=None
290 self.client_bootstrap=None
292 ### suitable if no reasonable command has been provided
293 def print_commands_help (self, options):
294 verbose=getattr(options,'verbose')
295 format3="%18s %-15s %s"
298 print format3%("command","cmd_args","description")
302 self.create_parser_global().print_help()
303 # preserve order from the code
304 for command in commands_list:
305 (doc, args_string, example) = commands_dict[command]
308 doc=doc.replace("\n","\n"+35*' ')
309 print format3%(command,args_string,doc)
311 self.create_parser_command(command).print_help()
313 ### now if a known command was found we can be more verbose on that one
314 def print_help (self):
315 print "==================== Generic sfi usage"
316 self.sfi_parser.print_help()
317 (doc,_,example)=commands_dict[self.command]
318 print "\n==================== Purpose of %s"%self.command
320 print "\n==================== Specific usage for %s"%self.command
321 self.command_parser.print_help()
323 print "\n==================== %s example(s)"%self.command
326 def create_parser_global(self):
327 # Generate command line parser
328 parser = OptionParser(add_help_option=False,
329 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
330 description="Commands: %s"%(" ".join(commands_list)))
331 parser.add_option("-r", "--registry", dest="registry",
332 help="root registry", metavar="URL", default=None)
333 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
334 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
335 parser.add_option("-R", "--raw", dest="raw", default=None,
336 help="Save raw, unparsed server response to a file")
337 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
338 help="raw file format ([text]|pickled|json)", default="text",
339 choices=("text","pickled","json"))
340 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
341 help="text string to write before and after raw output")
342 parser.add_option("-d", "--dir", dest="sfi_dir",
343 help="config & working directory - default is %default",
344 metavar="PATH", default=Sfi.default_sfi_dir())
345 parser.add_option("-u", "--user", dest="user",
346 help="user name", metavar="HRN", default=None)
347 parser.add_option("-a", "--auth", dest="auth",
348 help="authority name", metavar="HRN", default=None)
349 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
350 help="verbose mode - cumulative")
351 parser.add_option("-D", "--debug",
352 action="store_true", dest="debug", default=False,
353 help="Debug (xml-rpc) protocol messages")
354 # would it make sense to use ~/.ssh/id_rsa as a default here ?
355 parser.add_option("-k", "--private-key",
356 action="store", dest="user_private_key", default=None,
357 help="point to the private key file to use if not yet installed in sfi_dir")
358 parser.add_option("-t", "--timeout", dest="timeout", default=None,
359 help="Amout of time to wait before timing out the request")
360 parser.add_option("-h", "--help",
361 action="store_true", dest="help", default=False,
362 help="one page summary on commands & exit")
363 parser.disable_interspersed_args()
368 def create_parser_command(self, command):
369 if command not in commands_dict:
370 msg="Invalid command\n"
372 msg += ','.join(commands_list)
373 self.logger.critical(msg)
376 # retrieve args_string
377 (_, args_string, __) = commands_dict[command]
379 parser = OptionParser(add_help_option=False,
380 usage="sfi [sfi_options] %s [cmd_options] %s"
381 % (command, args_string))
382 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
383 help="Summary of one command usage")
385 if command in ("config"):
386 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
387 help='how myslice config variables as well')
389 if command in ("version"):
390 parser.add_option("-R","--registry-version",
391 action="store_true", dest="version_registry", default=False,
392 help="probe registry version instead of sliceapi")
393 parser.add_option("-l","--local",
394 action="store_true", dest="version_local", default=False,
395 help="display version of the local client")
397 if command in ("add", "update"):
398 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
399 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
400 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
401 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
403 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
404 default='', type="str", action='callback', callback=optparse_listvalue_callback)
405 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
406 help='Set/replace slice researchers', default='', type="str", action='callback',
407 callback=optparse_listvalue_callback)
408 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
409 default='', type="str", action='callback', callback=optparse_listvalue_callback)
410 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
411 action="callback", callback=optparse_dictvalue_callback, nargs=1,
412 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
414 # user specifies remote aggregate/sm/component
415 if command in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
416 "action", "shutdown", "renew", "status"):
417 parser.add_option("-d", "--delegate", dest="delegate", default=None,
419 help="Include a credential delegated to the user's root"+\
420 "authority in set of credentials for this call")
422 # show_credential option
423 if command in ("list","resources", "describe", "provision", "allocate", "add","update","remove","delete","status","renew"):
424 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
425 help="show credential(s) used in human-readable form")
426 # registy filter option
427 if command in ("list", "show", "remove"):
428 parser.add_option("-t", "--type", dest="type", type="choice",
429 help="type filter ([all]|user|slice|authority|node|aggregate)",
430 choices=("all", "user", "slice", "authority", "node", "aggregate"),
432 if command in ("show"):
433 parser.add_option("-k","--key",dest="keys",action="append",default=[],
434 help="specify specific keys to be displayed from record")
435 if command in ("resources", "describe"):
437 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
438 help="schema type and version of resulting RSpec")
439 # disable/enable cached rspecs
440 parser.add_option("-c", "--current", dest="current", default=False,
442 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
444 parser.add_option("-f", "--format", dest="format", type="choice",
445 help="display format ([xml]|dns|ip)", default="xml",
446 choices=("xml", "dns", "ip"))
447 #panos: a new option to define the type of information about resources a user is interested in
448 parser.add_option("-i", "--info", dest="info",
449 help="optional component information", default=None)
450 # a new option to retreive or not reservation-oriented RSpecs (leases)
451 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
452 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
453 choices=("all", "resources", "leases"), default="resources")
456 if command in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
457 parser.add_option("-o", "--output", dest="file",
458 help="output XML to file", metavar="FILE", default=None)
460 if command in ("show", "list"):
461 parser.add_option("-f", "--format", dest="format", type="choice",
462 help="display format ([text]|xml)", default="text",
463 choices=("text", "xml"))
465 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
466 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
467 choices=("xml", "xmllist", "hrnlist"))
468 if command == 'list':
469 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
470 help="list all child records", default=False)
471 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
472 help="gives details, like user keys", default=False)
473 if command in ("delegate"):
474 parser.add_option("-u", "--user",
475 action="store_true", dest="delegate_user", default=False,
476 help="delegate your own credentials; default if no other option is provided")
477 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
478 metavar="slice_hrn", help="delegate cred. for slice HRN")
479 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
480 metavar='auth_hrn', help="delegate cred for auth HRN")
481 # this primarily is a shorthand for -a my_hrn
482 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
483 help="delegate your PI credentials, so s.t. like -a your_hrn^")
484 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
485 help="""by default the mandatory argument is expected to be a user,
486 use this if you mean an authority instead""")
488 if command in ("myslice"):
489 parser.add_option("-p","--password",dest='password',action='store',default=None,
490 help="specify mainfold password on the command line")
491 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
492 metavar="slice_hrn", help="delegate cred. for slice HRN")
493 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
494 metavar='auth_hrn', help="delegate PI cred for auth HRN")
495 parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
496 parser.add_option('-b', '--backend', dest='backend', help="Override 'backend' from the config file")
502 # Main: parse arguments and dispatch to command
504 def dispatch(self, command, command_options, command_args):
505 method=getattr(self, command, None)
507 print "Unknown command %s"%command
509 return method(command_options, command_args)
512 self.sfi_parser = self.create_parser_global()
513 (options, args) = self.sfi_parser.parse_args()
515 self.print_commands_help(options)
517 self.options = options
519 self.logger.setLevelFromOptVerbose(self.options.verbose)
522 self.logger.critical("No command given. Use -h for help.")
523 self.print_commands_help(options)
526 # complete / find unique match with command set
527 command_candidates = Candidates (commands_list)
529 command = command_candidates.only_match(input)
531 self.print_commands_help(options)
533 # second pass options parsing
535 self.command_parser = self.create_parser_command(command)
536 (command_options, command_args) = self.command_parser.parse_args(args[1:])
537 if command_options.help:
540 self.command_options = command_options
544 self.logger.debug("Command=%s" % self.command)
547 self.dispatch(command, command_options, command_args)
551 self.logger.log_exc ("sfi command %s failed"%command)
557 def read_config(self):
558 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
559 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
561 if Config.is_ini(config_file):
562 config = Config (config_file)
564 # try upgrading from shell config format
565 fp, fn = mkstemp(suffix='sfi_config', text=True)
567 # we need to preload the sections we want parsed
568 # from the shell config
569 config.add_section('sfi')
570 # sface users should be able to use this same file to configure their stuff
571 config.add_section('sface')
572 # manifold users should be able to specify the details
573 # of their backend server here for 'sfi myslice'
574 config.add_section('myslice')
575 config.load(config_file)
577 shutil.move(config_file, shell_config_file)
579 config.save(config_file)
582 self.logger.critical("Failed to read configuration file %s"%config_file)
583 self.logger.info("Make sure to remove the export clauses and to add quotes")
584 if self.options.verbose==0:
585 self.logger.info("Re-run with -v for more details")
587 self.logger.log_exc("Could not read config file %s"%config_file)
590 self.config_instance=config
593 if (self.options.sm is not None):
594 self.sm_url = self.options.sm
595 elif hasattr(config, "SFI_SM"):
596 self.sm_url = config.SFI_SM
598 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
602 if (self.options.registry is not None):
603 self.reg_url = self.options.registry
604 elif hasattr(config, "SFI_REGISTRY"):
605 self.reg_url = config.SFI_REGISTRY
607 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
611 if (self.options.user is not None):
612 self.user = self.options.user
613 elif hasattr(config, "SFI_USER"):
614 self.user = config.SFI_USER
616 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
620 if (self.options.auth is not None):
621 self.authority = self.options.auth
622 elif hasattr(config, "SFI_AUTH"):
623 self.authority = config.SFI_AUTH
625 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
628 self.config_file=config_file
633 # Get various credential and spec files
635 # Establishes limiting conventions
636 # - conflates MAs and SAs
637 # - assumes last token in slice name is unique
639 # Bootstraps credentials
640 # - bootstrap user credential from self-signed certificate
641 # - bootstrap authority credential from user credential
642 # - bootstrap slice credential from user credential
645 # init self-signed cert, user credentials and gid
646 def bootstrap (self):
647 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
649 # if -k is provided, use this to initialize private key
650 if self.options.user_private_key:
651 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
653 # trigger legacy compat code if needed
654 # the name has changed from just <leaf>.pkey to <hrn>.pkey
655 if not os.path.isfile(client_bootstrap.private_key_filename()):
656 self.logger.info ("private key not found, trying legacy name")
658 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
659 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
660 client_bootstrap.init_private_key_if_missing (legacy_private_key)
661 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
663 self.logger.log_exc("Can't find private key ")
667 client_bootstrap.bootstrap_my_gid()
668 # extract what's needed
669 self.private_key = client_bootstrap.private_key()
670 self.my_credential_string = client_bootstrap.my_credential_string ()
671 self.my_credential = {'geni_type': 'geni_sfa',
673 'geni_value': self.my_credential_string}
674 self.my_gid = client_bootstrap.my_gid ()
675 self.client_bootstrap = client_bootstrap
678 def my_authority_credential_string(self):
679 if not self.authority:
680 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
682 return self.client_bootstrap.authority_credential_string (self.authority)
684 def authority_credential_string(self, auth_hrn):
685 return self.client_bootstrap.authority_credential_string (auth_hrn)
687 def slice_credential_string(self, name):
688 return self.client_bootstrap.slice_credential_string (name)
690 def slice_credential(self, name):
691 return {'geni_type': 'geni_sfa',
693 'geni_value': self.slice_credential_string(name)}
695 # xxx should be supported by sfaclientbootstrap as well
696 def delegate_cred(self, object_cred, hrn, type='authority'):
697 # the gid and hrn of the object we are delegating
698 if isinstance(object_cred, str):
699 object_cred = Credential(string=object_cred)
700 object_gid = object_cred.get_gid_object()
701 object_hrn = object_gid.get_hrn()
703 if not object_cred.get_privileges().get_all_delegate():
704 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
707 # the delegating user's gid
708 caller_gidfile = self.my_gid()
710 # the gid of the user who will be delegated to
711 delegee_gid = self.client_bootstrap.gid(hrn,type)
712 delegee_hrn = delegee_gid.get_hrn()
713 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
714 return dcred.save_to_string(save_parents=True)
717 # Management of the servers
722 if not hasattr (self, 'registry_proxy'):
723 self.logger.info("Contacting Registry at: %s"%self.reg_url)
724 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
725 timeout=self.options.timeout, verbose=self.options.debug)
726 return self.registry_proxy
730 if not hasattr (self, 'sliceapi_proxy'):
731 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
732 if hasattr(self.command_options,'component') and self.command_options.component:
733 # resolve the hrn at the registry
734 node_hrn = self.command_options.component
735 records = self.registry().Resolve(node_hrn, self.my_credential_string)
736 records = filter_records('node', records)
738 self.logger.warning("No such component:%r"% opts.component)
740 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
741 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
743 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
744 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
745 self.sm_url = 'http://' + self.sm_url
746 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
747 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
748 timeout=self.options.timeout, verbose=self.options.debug)
749 return self.sliceapi_proxy
751 def get_cached_server_version(self, server):
752 # check local cache first
755 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
756 cache_key = server.url + "-version"
758 cache = Cache(cache_file)
761 self.logger.info("Local cache not found at: %s" % cache_file)
764 version = cache.get(cache_key)
767 result = server.GetVersion()
768 version= ReturnValue.get_value(result)
769 # cache version for 20 minutes
770 cache.add(cache_key, version, ttl= 60*20)
771 self.logger.info("Updating cache file %s" % cache_file)
772 cache.save_to_file(cache_file)
776 ### resurrect this temporarily so we can support V1 aggregates for a while
777 def server_supports_options_arg(self, server):
779 Returns true if server support the optional call_id arg, false otherwise.
781 server_version = self.get_cached_server_version(server)
783 # xxx need to rewrite this
784 if int(server_version.get('geni_api')) >= 2:
788 def server_supports_call_id_arg(self, server):
789 server_version = self.get_cached_server_version(server)
791 if 'sfa' in server_version and 'code_tag' in server_version:
792 code_tag = server_version['code_tag']
793 code_tag_parts = code_tag.split("-")
794 version_parts = code_tag_parts[0].split(".")
795 major, minor = version_parts[0], version_parts[1]
796 rev = code_tag_parts[1]
797 if int(major) == 1 and minor == 0 and build >= 22:
801 ### ois = options if supported
802 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
803 def ois (self, server, option_dict):
804 if self.server_supports_options_arg (server):
806 elif self.server_supports_call_id_arg (server):
807 return [ unique_call_id () ]
811 ### cis = call_id if supported - like ois
812 def cis (self, server):
813 if self.server_supports_call_id_arg (server):
814 return [ unique_call_id ]
818 ######################################## miscell utilities
819 def get_rspec_file(self, rspec):
820 if (os.path.isabs(rspec)):
823 file = os.path.join(self.options.sfi_dir, rspec)
824 if (os.path.isfile(file)):
827 self.logger.critical("No such rspec file %s"%rspec)
830 def get_record_file(self, record):
831 if (os.path.isabs(record)):
834 file = os.path.join(self.options.sfi_dir, record)
835 if (os.path.isfile(file)):
838 self.logger.critical("No such registry record file %s"%record)
842 #==========================================================================
843 # Following functions implement the commands
845 # Registry-related commands
846 #==========================================================================
848 @register_command("","")
849 def config (self, options, args):
850 "Display contents of current config"
851 print "# From configuration file %s"%self.config_file
852 flags=[ ('sfi', [ ('registry','reg_url'),
853 ('auth','authority'),
859 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
861 for (section, tuples) in flags:
864 for (external_name, internal_name) in tuples:
865 print "%-20s = %s"%(external_name,getattr(self,internal_name))
868 varname="%s_%s"%(section.upper(),name.upper())
869 value=getattr(self.config_instance,varname)
870 print "%-20s = %s"%(name,value)
872 @register_command("","")
873 def version(self, options, args):
875 display an SFA server version (GetVersion)
876 or version information about sfi itself
878 if options.version_local:
879 version=version_core()
881 if options.version_registry:
882 server=self.registry()
884 server = self.sliceapi()
885 result = server.GetVersion()
886 version = ReturnValue.get_value(result)
888 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
890 pprinter = PrettyPrinter(indent=4)
891 pprinter.pprint(version)
893 @register_command("authority","")
894 def list(self, options, args):
896 list entries in named authority registry (List)
903 if options.recursive:
904 opts['recursive'] = options.recursive
906 if options.show_credential:
907 show_credentials(self.my_credential_string)
909 list = self.registry().List(hrn, self.my_credential_string, options)
911 raise Exception, "Not enough parameters for the 'list' command"
913 # filter on person, slice, site, node, etc.
914 # This really should be in the self.filter_records funct def comment...
915 list = filter_records(options.type, list)
916 terminal_render (list, options)
918 save_records_to_file(options.file, list, options.fileformat)
921 @register_command("name","")
922 def show(self, options, args):
924 show details about named registry record (Resolve)
930 # explicitly require Resolve to run in details mode
931 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
932 record_dicts = filter_records(options.type, record_dicts)
934 self.logger.error("No record of type %s"% options.type)
936 # user has required to focus on some keys
938 def project (record):
940 for key in options.keys:
941 try: projected[key]=record[key]
944 record_dicts = [ project (record) for record in record_dicts ]
945 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
946 for record in records:
947 if (options.format == "text"): record.dump(sort=True)
948 else: print record.save_as_xml()
950 save_records_to_file(options.file, record_dicts, options.fileformat)
953 @register_command("[xml-filename]","")
954 def add(self, options, args):
955 """add record into registry (Register)
956 from command line options (recommended)
957 old-school method involving an xml file still supported"""
959 auth_cred = self.my_authority_credential_string()
960 if options.show_credential:
961 show_credentials(auth_cred)
968 record_filepath = args[0]
969 rec_file = self.get_record_file(record_filepath)
970 record_dict.update(load_record_from_file(rec_file).todict())
972 print "Cannot load record file %s"%record_filepath
975 record_dict.update(load_record_from_opts(options).todict())
976 # we should have a type by now
977 if 'type' not in record_dict :
980 # this is still planetlab dependent.. as plc will whine without that
981 # also, it's only for adding
982 if record_dict['type'] == 'user':
983 if not 'first_name' in record_dict:
984 record_dict['first_name'] = record_dict['hrn']
985 if 'last_name' not in record_dict:
986 record_dict['last_name'] = record_dict['hrn']
987 return self.registry().Register(record_dict, auth_cred)
989 @register_command("[xml-filename]","")
990 def update(self, options, args):
991 """update record into registry (Update)
992 from command line options (recommended)
993 old-school method involving an xml file still supported"""
996 record_filepath = args[0]
997 rec_file = self.get_record_file(record_filepath)
998 record_dict.update(load_record_from_file(rec_file).todict())
1000 record_dict.update(load_record_from_opts(options).todict())
1001 # at the very least we need 'type' here
1002 if 'type' not in record_dict:
1006 # don't translate into an object, as this would possibly distort
1007 # user-provided data; e.g. add an 'email' field to Users
1008 if record_dict['type'] == "user":
1009 if record_dict['hrn'] == self.user:
1010 cred = self.my_credential_string
1012 cred = self.my_authority_credential_string()
1013 elif record_dict['type'] in ["slice"]:
1015 cred = self.slice_credential_string(record_dict['hrn'])
1016 except ServerException, e:
1017 # XXX smbaker -- once we have better error return codes, update this
1018 # to do something better than a string compare
1019 if "Permission error" in e.args[0]:
1020 cred = self.my_authority_credential_string()
1023 elif record_dict['type'] in ["authority"]:
1024 cred = self.my_authority_credential_string()
1025 elif record_dict['type'] == 'node':
1026 cred = self.my_authority_credential_string()
1028 raise "unknown record type" + record_dict['type']
1029 if options.show_credential:
1030 show_credentials(cred)
1031 return self.registry().Update(record_dict, cred)
1033 @register_command("hrn","")
1034 def remove(self, options, args):
1035 "remove registry record by name (Remove)"
1036 auth_cred = self.my_authority_credential_string()
1044 if options.show_credential:
1045 show_credentials(auth_cred)
1046 return self.registry().Remove(hrn, auth_cred, type)
1048 # ==================================================================
1049 # Slice-related commands
1050 # ==================================================================
1052 # show rspec for named slice
1053 @register_command("","")
1054 def resources(self, options, args):
1056 discover available resources (ListResources)
1058 server = self.sliceapi()
1061 creds = [self.my_credential]
1062 if options.delegate:
1063 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1064 if options.show_credential:
1065 show_credentials(creds)
1067 # no need to check if server accepts the options argument since the options has
1068 # been a required argument since v1 API
1070 # always send call_id to v2 servers
1071 api_options ['call_id'] = unique_call_id()
1072 # ask for cached value if available
1073 api_options ['cached'] = True
1075 api_options['info'] = options.info
1076 if options.list_leases:
1077 api_options['list_leases'] = options.list_leases
1079 if options.current == True:
1080 api_options['cached'] = False
1082 api_options['cached'] = True
1083 if options.rspec_version:
1084 version_manager = VersionManager()
1085 server_version = self.get_cached_server_version(server)
1086 if 'sfa' in server_version:
1087 # just request the version the client wants
1088 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1090 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1092 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1093 result = server.ListResources (creds, api_options)
1094 value = ReturnValue.get_value(result)
1095 if self.options.raw:
1096 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1097 if options.file is not None:
1098 save_rspec_to_file(value, options.file)
1099 if (self.options.raw is None) and (options.file is None):
1100 display_rspec(value, options.format)
1104 @register_command("slice_hrn","")
1105 def describe(self, options, args):
1107 shows currently allocated/provisioned resources
1108 of the named slice or set of slivers (Describe)
1110 server = self.sliceapi()
1113 creds = [self.slice_credential(args[0])]
1114 if options.delegate:
1115 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1116 if options.show_credential:
1117 show_credentials(creds)
1119 api_options = {'call_id': unique_call_id(),
1121 'info': options.info,
1122 'list_leases': options.list_leases,
1123 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1125 if options.rspec_version:
1126 version_manager = VersionManager()
1127 server_version = self.get_cached_server_version(server)
1128 if 'sfa' in server_version:
1129 # just request the version the client wants
1130 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1132 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1133 urn = Xrn(args[0], type='slice').get_urn()
1134 result = server.Describe([urn], creds, api_options)
1135 value = ReturnValue.get_value(result)
1136 if self.options.raw:
1137 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1138 if options.file is not None:
1139 save_rspec_to_file(value, options.file)
1140 if (self.options.raw is None) and (options.file is None):
1141 display_rspec(value, options.format)
1145 @register_command("slice_hrn","")
1146 def delete(self, options, args):
1148 de-allocate and de-provision all or named slivers of the slice (Delete)
1150 server = self.sliceapi()
1154 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1157 slice_cred = self.slice_credential(slice_hrn)
1158 creds = [slice_cred]
1160 # options and call_id when supported
1162 api_options ['call_id'] = unique_call_id()
1163 if options.show_credential:
1164 show_credentials(creds)
1165 result = server.Delete([slice_urn], creds, *self.ois(server, api_options ) )
1166 value = ReturnValue.get_value(result)
1167 if self.options.raw:
1168 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1173 @register_command("slice_hrn rspec","")
1174 def allocate(self, options, args):
1176 allocate resources to the named slice (Allocate)
1178 server = self.sliceapi()
1179 server_version = self.get_cached_server_version(server)
1181 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1184 creds = [self.slice_credential(slice_hrn)]
1186 delegated_cred = None
1187 if server_version.get('interface') == 'slicemgr':
1188 # delegate our cred to the slice manager
1189 # do not delegate cred to slicemgr...not working at the moment
1191 #if server_version.get('hrn'):
1192 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1193 #elif server_version.get('urn'):
1194 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1196 if options.show_credential:
1197 show_credentials(creds)
1200 rspec_file = self.get_rspec_file(args[1])
1201 rspec = open(rspec_file).read()
1203 api_options ['call_id'] = unique_call_id()
1207 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1208 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1209 slice_record = slice_records[0]
1210 user_hrns = slice_record['reg-researchers']
1211 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1212 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1213 sfa_users = sfa_users_arg(user_records, slice_record)
1214 geni_users = pg_users_arg(user_records)
1216 api_options['sfa_users'] = sfa_users
1217 api_options['geni_users'] = geni_users
1219 result = server.Allocate(slice_urn, creds, rspec, api_options)
1220 value = ReturnValue.get_value(result)
1221 if self.options.raw:
1222 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1223 if options.file is not None:
1224 save_rspec_to_file (value, options.file)
1225 if (self.options.raw is None) and (options.file is None):
1230 @register_command("slice_hrn","")
1231 def provision(self, options, args):
1233 provision already allocated resources of named slice (Provision)
1235 server = self.sliceapi()
1236 server_version = self.get_cached_server_version(server)
1238 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1241 creds = [self.slice_credential(slice_hrn)]
1242 delegated_cred = None
1243 if server_version.get('interface') == 'slicemgr':
1244 # delegate our cred to the slice manager
1245 # do not delegate cred to slicemgr...not working at the moment
1247 #if server_version.get('hrn'):
1248 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1249 #elif server_version.get('urn'):
1250 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1252 if options.show_credential:
1253 show_credentials(creds)
1256 api_options ['call_id'] = unique_call_id()
1258 # set the requtested rspec version
1259 version_manager = VersionManager()
1260 rspec_version = version_manager._get_version('geni', '3').to_dict()
1261 api_options['geni_rspec_version'] = rspec_version
1264 # need to pass along user keys to the aggregate.
1266 # { urn: urn:publicid:IDN+emulab.net+user+alice
1267 # keys: [<ssh key A>, <ssh key B>]
1270 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1271 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1272 slice_record = slice_records[0]
1273 user_hrns = slice_record['reg-researchers']
1274 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1275 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1276 users = pg_users_arg(user_records)
1278 api_options['geni_users'] = users
1279 result = server.Provision([slice_urn], creds, api_options)
1280 value = ReturnValue.get_value(result)
1281 if self.options.raw:
1282 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1283 if options.file is not None:
1284 save_rspec_to_file (value, options.file)
1285 if (self.options.raw is None) and (options.file is None):
1289 @register_command("slice_hrn","")
1290 def status(self, options, args):
1292 retrieve the status of the slivers belonging to tne named slice (Status)
1294 server = self.sliceapi()
1298 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1301 slice_cred = self.slice_credential(slice_hrn)
1302 creds = [slice_cred]
1304 # options and call_id when supported
1306 api_options['call_id']=unique_call_id()
1307 if options.show_credential:
1308 show_credentials(creds)
1309 result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1310 value = ReturnValue.get_value(result)
1311 if self.options.raw:
1312 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1315 # Thierry: seemed to be missing
1318 @register_command("slice_hrn action","")
1319 def action(self, options, args):
1321 Perform the named operational action on these slivers
1323 server = self.sliceapi()
1328 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1330 slice_cred = self.slice_credential(args[0])
1331 creds = [slice_cred]
1332 if options.delegate:
1333 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1334 creds.append(delegated_cred)
1336 result = server.PerformOperationalAction([slice_urn], creds, action , api_options)
1337 value = ReturnValue.get_value(result)
1338 if self.options.raw:
1339 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1344 @register_command("slice_hrn time","")
1345 def renew(self, options, args):
1347 renew slice (RenewSliver)
1349 server = self.sliceapi()
1353 [ slice_hrn, input_time ] = args
1355 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1356 # time: don't try to be smart on the time format, server-side will
1358 slice_cred = self.slice_credential(args[0])
1359 creds = [slice_cred]
1360 # options and call_id when supported
1362 api_options['call_id']=unique_call_id()
1363 if options.show_credential:
1364 show_credentials(creds)
1365 result = server.Renew([slice_urn], creds, input_time, *self.ois(server,api_options))
1366 value = ReturnValue.get_value(result)
1367 if self.options.raw:
1368 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1374 @register_command("slice_hrn","")
1375 def shutdown(self, options, args):
1377 shutdown named slice (Shutdown)
1379 server = self.sliceapi()
1382 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1384 slice_cred = self.slice_credential(slice_hrn)
1385 creds = [slice_cred]
1386 result = server.Shutdown(slice_urn, creds)
1387 value = ReturnValue.get_value(result)
1388 if self.options.raw:
1389 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1395 @register_command("[name]","")
1396 def gid(self, options, args):
1398 Create a GID (CreateGid)
1403 target_hrn = args[0]
1404 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1405 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1407 filename = options.file
1409 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1410 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1411 GID(string=gid).save_to_file(filename)
1413 ####################
1414 @register_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1416 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1417 the set of credentials in the scope for this call would be
1418 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1420 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1422 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1423 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1424 because of the two -s options
1427 def delegate (self, options, args):
1429 (locally) create delegate credential for use by given hrn
1430 make sure to check for 'sfi myslice' instead if you plan
1437 # support for several delegations in the same call
1438 # so first we gather the things to do
1440 for slice_hrn in options.delegate_slices:
1441 message="%s.slice"%slice_hrn
1442 original = self.slice_credential_string(slice_hrn)
1443 tuples.append ( (message, original,) )
1444 if options.delegate_pi:
1445 my_authority=self.authority
1446 message="%s.pi"%my_authority
1447 original = self.my_authority_credential_string()
1448 tuples.append ( (message, original,) )
1449 for auth_hrn in options.delegate_auths:
1450 message="%s.auth"%auth_hrn
1451 original=self.authority_credential_string(auth_hrn)
1452 tuples.append ( (message, original, ) )
1453 # if nothing was specified at all at this point, let's assume -u
1454 if not tuples: options.delegate_user=True
1456 if options.delegate_user:
1457 message="%s.user"%self.user
1458 original = self.my_credential_string
1459 tuples.append ( (message, original, ) )
1461 # default type for beneficial is user unless -A
1462 if options.delegate_to_authority: to_type='authority'
1463 else: to_type='user'
1465 # let's now handle all this
1466 # it's all in the filenaming scheme
1467 for (message,original) in tuples:
1468 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1469 delegated_credential = Credential (string=delegated_string)
1470 filename = os.path.join ( self.options.sfi_dir,
1471 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1472 delegated_credential.save_to_file(filename, save_parents=True)
1473 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1475 ####################
1476 @register_command("","""$ less +/myslice sfi_config
1478 backend = http://manifold.pl.sophia.inria.fr:7080
1479 # the HRN that myslice uses, so that we are delegating to
1480 delegate = ple.upmc.slicebrowser
1481 # platform - this is a myslice concept
1483 # username - as of this writing (May 2013) a simple login name
1487 will first collect the slices that you are part of, then make sure
1488 all your credentials are up-to-date (read: refresh expired ones)
1489 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1490 and upload them all on myslice backend, using 'platform' and 'user'.
1491 A password will be prompted for the upload part.
1493 $ sfi -v myslice -- or sfi -vv myslice
1494 same but with more and more verbosity
1496 $ sfi m -b http://mymanifold.foo.com:7080/
1497 is synonym to sfi myslice as no other command starts with an 'm'
1498 and uses a custom backend for this one call
1500 ) # register_command
1501 def myslice (self, options, args):
1503 """ This helper is for refreshing your credentials at myslice; it will
1504 * compute all the slices that you currently have credentials on
1505 * refresh all your credentials (you as a user and pi, your slices)
1506 * upload them to the manifold backend server
1507 for last phase, sfi_config is read to look for the [myslice] section,
1508 and namely the 'backend', 'delegate' and 'user' settings"""
1514 # enable info by default
1515 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1516 ### the rough sketch goes like this
1517 # (0) produce a p12 file
1518 self.client_bootstrap.my_pkcs12()
1520 # (a) rain check for sufficient config in sfi_config
1522 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1523 for key in myslice_keys:
1525 # oct 2013 - I'm finding myself juggling with config files
1526 # so a couple of command-line options can now override config
1527 if hasattr(options,key) and getattr(options,key) is not None:
1528 value=getattr(options,key)
1530 full_key="MYSLICE_" + key.upper()
1531 value=getattr(self.config_instance,full_key,None)
1532 if value: myslice_dict[key]=value
1533 else: print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1534 if len(myslice_dict) != len(myslice_keys):
1537 # (b) figure whether we are PI for the authority where we belong
1538 self.logger.info("Resolving our own id %s"%self.user)
1539 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1540 if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1541 my_record=my_records[0]
1542 my_auths_all = my_record['reg-pi-authorities']
1543 self.logger.info("Found %d authorities that we are PI for"%len(my_auths_all))
1544 self.logger.debug("They are %s"%(my_auths_all))
1546 my_auths = my_auths_all
1547 if options.delegate_auths:
1548 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1549 self.logger.debug("Restricted to user-provided auths"%(my_auths))
1551 # (c) get the set of slices that we are in
1552 my_slices_all=my_record['reg-slices']
1553 self.logger.info("Found %d slices that we are member of"%len(my_slices_all))
1554 self.logger.debug("They are: %s"%(my_slices_all))
1556 my_slices = my_slices_all
1557 # if user provided slices, deal only with these - if they are found
1558 if options.delegate_slices:
1559 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1560 self.logger.debug("Restricted to user-provided slices: %s"%(my_slices))
1562 # (d) make sure we have *valid* credentials for all these
1564 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1565 for auth_hrn in my_auths:
1566 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1567 for slice_hrn in my_slices:
1568 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1570 # (e) check for the delegated version of these
1571 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1572 # switch to myslice using an authority instead of a user
1573 delegatee_type='user'
1574 delegatee_hrn=myslice_dict['delegate']
1575 hrn_delegated_credentials = []
1576 for (hrn, htype, credential) in hrn_credentials:
1577 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1578 # save these so user can monitor what she's uploaded
1579 filename = os.path.join ( self.options.sfi_dir,
1580 "%s.%s_for_%s.%s.cred"%(hrn,htype,delegatee_hrn,delegatee_type))
1581 with file(filename,'w') as f:
1582 f.write(delegated_credential)
1583 self.logger.debug("(Over)wrote %s"%filename)
1584 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1586 # (f) and finally upload them to manifold server
1587 # xxx todo add an option so the password can be set on the command line
1588 # (but *NOT* in the config file) so other apps can leverage this
1589 self.logger.info("Uploading on backend at %s"%myslice_dict['backend'])
1590 uploader = ManifoldUploader (logger=self.logger,
1591 url=myslice_dict['backend'],
1592 platform=myslice_dict['platform'],
1593 username=myslice_dict['username'],
1594 password=options.password)
1595 uploader.prompt_all()
1596 (count_all,count_success)=(0,0)
1597 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1599 inspect=Credential(string=delegated_credential)
1600 expire_datetime=inspect.get_expiration()
1601 message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
1602 if uploader.upload(delegated_credential,message=message):
1605 self.logger.info("Successfully uploaded %d/%d credentials"%(count_success,count_all))
1607 # at first I thought we would want to save these,
1608 # like 'sfi delegate does' but on second thought
1609 # it is probably not helpful as people would not
1610 # need to run 'sfi delegate' at all anymore
1611 if count_success != count_all: sys.exit(1)
1614 @register_command("cred","")
1615 def trusted(self, options, args):
1617 return the trusted certs at this interface (get_trusted_certs)
1619 trusted_certs = self.registry().get_trusted_certs()
1620 for trusted_cert in trusted_certs:
1621 print "\n===========================================================\n"
1622 gid = GID(string=trusted_cert)
1624 cert = Certificate(string=trusted_cert)
1625 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1626 print "Certificate:\n%s\n\n"%trusted_cert