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 credential_printable (credential_string):
90 credential=Credential(string=credential_string)
92 result += credential.get_summary_tostring()
94 rights = credential.get_privileges()
95 result += "rights=%s"%rights
99 def show_credentials (cred_s):
100 if not isinstance (cred_s,list): cred_s = [cred_s]
102 print "Using Credential %s"%credential_printable(cred)
105 def save_raw_to_file(var, filename, format="text", banner=None):
107 # if filename is "-", send it to stdout
110 f = open(filename, "w")
115 elif format == "pickled":
116 f.write(pickle.dumps(var))
117 elif format == "json":
118 if hasattr(json, "dumps"):
119 f.write(json.dumps(var)) # python 2.6
121 f.write(json.write(var)) # python 2.5
123 # this should never happen
124 print "unknown output format", format
126 f.write('\n'+banner+"\n")
128 def save_rspec_to_file(rspec, filename):
129 if not filename.endswith(".rspec"):
130 filename = filename + ".rspec"
131 f = open(filename, 'w')
136 def save_records_to_file(filename, record_dicts, format="xml"):
139 for record_dict in record_dicts:
141 save_record_to_file(filename + "." + str(index), record_dict)
143 save_record_to_file(filename, record_dict)
145 elif format == "xmllist":
146 f = open(filename, "w")
147 f.write("<recordlist>\n")
148 for record_dict in record_dicts:
149 record_obj=Record(dict=record_dict)
150 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
151 f.write("</recordlist>\n")
153 elif format == "hrnlist":
154 f = open(filename, "w")
155 for record_dict in record_dicts:
156 record_obj=Record(dict=record_dict)
157 f.write(record_obj.hrn + "\n")
160 # this should never happen
161 print "unknown output format", format
163 def save_record_to_file(filename, record_dict):
164 record = Record(dict=record_dict)
165 xml = record.save_as_xml()
166 f=codecs.open(filename, encoding='utf-8',mode="w")
171 # minimally check a key argument
172 def check_ssh_key (key):
173 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
174 return re.match(good_ssh_key, key, re.IGNORECASE)
177 def load_record_from_opts(options):
179 if hasattr(options, 'xrn') and options.xrn:
180 if hasattr(options, 'type') and options.type:
181 xrn = Xrn(options.xrn, options.type)
183 xrn = Xrn(options.xrn)
184 record_dict['urn'] = xrn.get_urn()
185 record_dict['hrn'] = xrn.get_hrn()
186 record_dict['type'] = xrn.get_type()
187 if hasattr(options, 'key') and options.key:
189 pubkey = open(options.key, 'r').read()
192 if not check_ssh_key (pubkey):
193 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
194 record_dict['keys'] = [pubkey]
195 if hasattr(options, 'slices') and options.slices:
196 record_dict['slices'] = options.slices
197 if hasattr(options, 'researchers') and options.researchers:
198 record_dict['researcher'] = options.researchers
199 if hasattr(options, 'email') and options.email:
200 record_dict['email'] = options.email
201 if hasattr(options, 'pis') and options.pis:
202 record_dict['pi'] = options.pis
204 # handle extra settings
205 record_dict.update(options.extras)
207 return Record(dict=record_dict)
209 def load_record_from_file(filename):
210 f=codecs.open(filename, encoding="utf-8", mode="r")
211 xml_string = f.read()
213 return Record(xml=xml_string)
217 def unique_call_id(): return uuid.uuid4().urn
219 ########## a simple model for maintaing 3 doc attributes per command (instead of just one)
220 # essentially for the methods that implement a subcommand like sfi list
221 # we need to keep track of
222 # (*) doc a few lines that tell what it does, still located in __doc__
223 # (*) args_string a simple one-liner that describes mandatory arguments
224 # (*) example well, one or several releant examples
226 # since __doc__ only accounts for one, we use this simple mechanism below
227 # however we keep doc in place for easier migration
229 from functools import wraps
231 # we use a list as well as a dict so we can keep track of the order
235 def register_command (args_string, example):
237 name=getattr(m,'__name__')
238 doc=getattr(m,'__doc__',"-- missing doc --")
239 doc=doc.strip(" \t\n")
240 commands_list.append(name)
241 commands_dict[name]=(doc, args_string, example)
243 def new_method (*args, **kwds): return m(*args, **kwds)
251 # dirty hack to make this class usable from the outside
252 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
255 def default_sfi_dir ():
256 if os.path.isfile("./sfi_config"):
259 return os.path.expanduser("~/.sfi/")
261 # dummy to meet Sfi's expectations for its 'options' field
262 # i.e. s/t we can do setattr on
266 def __init__ (self,options=None):
267 if options is None: options=Sfi.DummyOptions()
268 for opt in Sfi.required_options:
269 if not hasattr(options,opt): setattr(options,opt,None)
270 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
271 self.options = options
273 self.authority = None
274 self.logger = sfi_logger
275 self.logger.enable_console()
276 ### various auxiliary material that we keep at hand
278 # need to call this other than just 'config' as we have a command/method with that name
279 self.config_instance=None
280 self.config_file=None
281 self.client_bootstrap=None
283 ### suitable if no reasonable command has been provided
284 def print_commands_help (self, options):
285 verbose=getattr(options,'verbose')
286 format3="%18s %-15s %s"
289 print format3%("command","cmd_args","description")
293 self.create_parser_global().print_help()
294 # preserve order from the code
295 for command in commands_list:
296 (doc, args_string, example) = commands_dict[command]
299 doc=doc.replace("\n","\n"+35*' ')
300 print format3%(command,args_string,doc)
302 self.create_parser_command(command).print_help()
304 ### now if a known command was found we can be more verbose on that one
305 def print_help (self):
306 print "==================== Generic sfi usage"
307 self.sfi_parser.print_help()
308 (doc,_,example)=commands_dict[self.command]
309 print "\n==================== Purpose of %s"%self.command
311 print "\n==================== Specific usage for %s"%self.command
312 self.command_parser.print_help()
314 print "\n==================== %s example(s)"%self.command
317 def create_parser_global(self):
319 # Generate command line parser
320 parser = OptionParser(add_help_option=False,
321 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
322 description="Commands: %s"%(" ".join(commands_list)))
323 parser.add_option("-r", "--registry", dest="registry",
324 help="root registry", metavar="URL", default=None)
325 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
326 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
327 parser.add_option("-R", "--raw", dest="raw", default=None,
328 help="Save raw, unparsed server response to a file")
329 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
330 help="raw file format ([text]|pickled|json)", default="text",
331 choices=("text","pickled","json"))
332 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
333 help="text string to write before and after raw output")
334 parser.add_option("-d", "--dir", dest="sfi_dir",
335 help="config & working directory - default is %default",
336 metavar="PATH", default=Sfi.default_sfi_dir())
337 parser.add_option("-u", "--user", dest="user",
338 help="user name", metavar="HRN", default=None)
339 parser.add_option("-a", "--auth", dest="auth",
340 help="authority name", metavar="HRN", default=None)
341 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
342 help="verbose mode - cumulative")
343 parser.add_option("-D", "--debug",
344 action="store_true", dest="debug", default=False,
345 help="Debug (xml-rpc) protocol messages")
346 # would it make sense to use ~/.ssh/id_rsa as a default here ?
347 parser.add_option("-k", "--private-key",
348 action="store", dest="user_private_key", default=None,
349 help="point to the private key file to use if not yet installed in sfi_dir")
350 parser.add_option("-t", "--timeout", dest="timeout", default=None,
351 help="Amout of time to wait before timing out the request")
352 parser.add_option("-h", "--help",
353 action="store_true", dest="help", default=False,
354 help="one page summary on commands & exit")
355 parser.disable_interspersed_args()
360 def create_parser_command(self, command):
361 if command not in commands_dict:
362 msg="Invalid command\n"
364 msg += ','.join(commands_list)
365 self.logger.critical(msg)
368 # retrieve args_string
369 (_, args_string, __) = commands_dict[command]
371 parser = OptionParser(add_help_option=False,
372 usage="sfi [sfi_options] %s [cmd_options] %s"
373 % (command, args_string))
374 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
375 help="Summary of one command usage")
377 if command in ("config"):
378 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
379 help='how myslice config variables as well')
381 if command in ("version"):
382 parser.add_option("-l","--local",
383 action="store_true", dest="version_local", default=False,
384 help="display version of the local client")
386 if command in ("version", "trusted"):
387 parser.add_option("-I", "--interface", dest="interface", type="choice",
388 help="Select the SFA interface the call should target (Slice Interface (sm) | Registry Interface (registry))",
389 choices=("sm", "registry"),
392 if command in ("trusted"):
393 parser.add_option("-R","--registry-version",
394 action="store_true", dest="version_registry", default=False,
395 help="probe registry version instead of sliceapi")
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 # show_credential option
415 if command in ("list","resources","create","add","update","remove","slices","delete","status","renew"):
416 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
417 help="show credential(s) used in human-readable form")
418 # registy filter option
419 if command in ("list", "show", "remove"):
420 parser.add_option("-t", "--type", dest="type", type="choice",
421 help="type filter ([all]|user|slice|authority|node|aggregate)",
422 choices=("all", "user", "slice", "authority", "node", "aggregate"),
424 if command in ("show"):
425 parser.add_option("-k","--key",dest="keys",action="append",default=[],
426 help="specify specific keys to be displayed from record")
427 if command in ("resources"):
429 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
430 help="schema type and version of resulting RSpec")
431 # disable/enable cached rspecs
432 parser.add_option("-c", "--current", dest="current", default=False,
434 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
436 parser.add_option("-f", "--format", dest="format", type="choice",
437 help="display format ([xml]|dns|ip)", default="xml",
438 choices=("xml", "dns", "ip"))
439 #panos: a new option to define the type of information about resources a user is interested in
440 parser.add_option("-i", "--info", dest="info",
441 help="optional component information", default=None)
442 # a new option to retreive or not reservation-oriented RSpecs (leases)
443 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
444 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
445 choices=("all", "resources", "leases"), default="resources")
448 # 'create' does return the new rspec, makes sense to save that too
449 if command in ("resources", "show", "list", "gid", 'create'):
450 parser.add_option("-o", "--output", dest="file",
451 help="output XML to file", metavar="FILE", default=None)
453 if command in ("show", "list"):
454 parser.add_option("-f", "--format", dest="format", type="choice",
455 help="display format ([text]|xml)", default="text",
456 choices=("text", "xml"))
458 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
459 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
460 choices=("xml", "xmllist", "hrnlist"))
461 if command == 'list':
462 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
463 help="list all child records", default=False)
464 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
465 help="gives details, like user keys", default=False)
466 if command in ("delegate"):
467 parser.add_option("-u", "--user",
468 action="store_true", dest="delegate_user", default=False,
469 help="delegate your own credentials; default if no other option is provided")
470 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
471 metavar="slice_hrn", help="delegate cred. for slice HRN")
472 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
473 metavar='auth_hrn', help="delegate cred for auth HRN")
474 # this primarily is a shorthand for -a my_hrn
475 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
476 help="delegate your PI credentials, so s.t. like -a your_hrn^")
477 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
478 help="""by default the mandatory argument is expected to be a user,
479 use this if you mean an authority instead""")
481 if command in ("myslice"):
482 parser.add_option("-p","--password",dest='password',action='store',default=None,
483 help="specify mainfold password on the command line")
484 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
485 metavar="slice_hrn", help="delegate cred. for slice HRN")
486 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
487 metavar='auth_hrn', help="delegate PI cred for auth HRN")
488 parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
489 parser.add_option('-b', '--backend', dest='backend', help="Override 'backend' from the config file")
495 # Main: parse arguments and dispatch to command
497 def dispatch(self, command, command_options, command_args):
498 method=getattr(self, command, None)
500 print "Unknown command %s"%command
502 return method(command_options, command_args)
505 self.sfi_parser = self.create_parser_global()
506 (options, args) = self.sfi_parser.parse_args()
508 self.print_commands_help(options)
510 self.options = options
512 self.logger.setLevelFromOptVerbose(self.options.verbose)
515 self.logger.critical("No command given. Use -h for help.")
516 self.print_commands_help(options)
519 # complete / find unique match with command set
520 command_candidates = Candidates (commands_list)
522 command = command_candidates.only_match(input)
524 self.print_commands_help(options)
526 # second pass options parsing
528 self.command_parser = self.create_parser_command(command)
529 (command_options, command_args) = self.command_parser.parse_args(args[1:])
530 if command_options.help:
533 self.command_options = command_options
537 self.logger.debug("Command=%s" % self.command)
540 self.dispatch(command, command_options, command_args)
544 self.logger.log_exc ("sfi command %s failed"%command)
550 def read_config(self):
551 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
552 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
554 if Config.is_ini(config_file):
555 config = Config (config_file)
557 # try upgrading from shell config format
558 fp, fn = mkstemp(suffix='sfi_config', text=True)
560 # we need to preload the sections we want parsed
561 # from the shell config
562 config.add_section('sfi')
563 # sface users should be able to use this same file to configure their stuff
564 config.add_section('sface')
565 # manifold users should be able to specify the details
566 # of their backend server here for 'sfi myslice'
567 config.add_section('myslice')
568 config.load(config_file)
570 shutil.move(config_file, shell_config_file)
572 config.save(config_file)
575 self.logger.critical("Failed to read configuration file %s"%config_file)
576 self.logger.info("Make sure to remove the export clauses and to add quotes")
577 if self.options.verbose==0:
578 self.logger.info("Re-run with -v for more details")
580 self.logger.log_exc("Could not read config file %s"%config_file)
583 self.config_instance=config
586 if (self.options.sm is not None):
587 self.sm_url = self.options.sm
588 elif hasattr(config, "SFI_SM"):
589 self.sm_url = config.SFI_SM
591 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
595 if (self.options.registry is not None):
596 self.reg_url = self.options.registry
597 elif hasattr(config, "SFI_REGISTRY"):
598 self.reg_url = config.SFI_REGISTRY
600 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
604 if (self.options.user is not None):
605 self.user = self.options.user
606 elif hasattr(config, "SFI_USER"):
607 self.user = config.SFI_USER
609 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
613 if (self.options.auth is not None):
614 self.authority = self.options.auth
615 elif hasattr(config, "SFI_AUTH"):
616 self.authority = config.SFI_AUTH
618 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
621 self.config_file=config_file
626 # Get various credential and spec files
628 # Establishes limiting conventions
629 # - conflates MAs and SAs
630 # - assumes last token in slice name is unique
632 # Bootstraps credentials
633 # - bootstrap user credential from self-signed certificate
634 # - bootstrap authority credential from user credential
635 # - bootstrap slice credential from user credential
638 # init self-signed cert, user credentials and gid
639 def bootstrap (self):
640 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
642 # if -k is provided, use this to initialize private key
643 if self.options.user_private_key:
644 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
646 # trigger legacy compat code if needed
647 # the name has changed from just <leaf>.pkey to <hrn>.pkey
648 if not os.path.isfile(client_bootstrap.private_key_filename()):
649 self.logger.info ("private key not found, trying legacy name")
651 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
652 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
653 client_bootstrap.init_private_key_if_missing (legacy_private_key)
654 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
656 self.logger.log_exc("Can't find private key ")
660 client_bootstrap.bootstrap_my_gid()
661 # extract what's needed
662 self.private_key = client_bootstrap.private_key()
663 self.my_credential_string = client_bootstrap.my_credential_string ()
664 self.my_gid = client_bootstrap.my_gid ()
665 self.client_bootstrap = client_bootstrap
668 def my_authority_credential_string(self):
669 if not self.authority:
670 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
672 return self.client_bootstrap.authority_credential_string (self.authority)
674 def authority_credential_string(self, auth_hrn):
675 return self.client_bootstrap.authority_credential_string (auth_hrn)
677 def slice_credential_string(self, name):
678 return self.client_bootstrap.slice_credential_string (name)
681 # Management of the servers
686 if not hasattr (self, 'registry_proxy'):
687 self.logger.info("Contacting Registry at: %s"%self.reg_url)
688 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
689 timeout=self.options.timeout, verbose=self.options.debug)
690 return self.registry_proxy
694 if not hasattr (self, 'sliceapi_proxy'):
695 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
696 if hasattr(self.command_options,'component') and self.command_options.component:
697 # resolve the hrn at the registry
698 node_hrn = self.command_options.component
699 records = self.registry().Resolve(node_hrn, self.my_credential_string)
700 records = filter_records('node', records)
702 self.logger.warning("No such component:%r"% opts.component)
704 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
705 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
707 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
708 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
709 self.sm_url = 'http://' + self.sm_url
710 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
711 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
712 timeout=self.options.timeout, verbose=self.options.debug)
713 return self.sliceapi_proxy
715 def get_cached_server_version(self, server):
716 # check local cache first
719 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
720 cache_key = server.url + "-version"
722 cache = Cache(cache_file)
725 self.logger.info("Local cache not found at: %s" % cache_file)
728 version = cache.get(cache_key)
731 result = server.GetVersion()
732 version= ReturnValue.get_value(result)
733 # cache version for 20 minutes
734 cache.add(cache_key, version, ttl= 60*20)
735 self.logger.info("Updating cache file %s" % cache_file)
736 cache.save_to_file(cache_file)
740 ### resurrect this temporarily so we can support V1 aggregates for a while
741 def server_supports_options_arg(self, server):
743 Returns true if server support the optional call_id arg, false otherwise.
745 server_version = self.get_cached_server_version(server)
747 # xxx need to rewrite this
748 if int(server_version.get('geni_api')) >= 2:
752 def server_supports_call_id_arg(self, server):
753 server_version = self.get_cached_server_version(server)
755 if 'sfa' in server_version and 'code_tag' in server_version:
756 code_tag = server_version['code_tag']
757 code_tag_parts = code_tag.split("-")
758 version_parts = code_tag_parts[0].split(".")
759 major, minor = version_parts[0], version_parts[1]
760 rev = code_tag_parts[1]
761 if int(major) == 1 and minor == 0 and build >= 22:
765 ### ois = options if supported
766 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
767 def ois (self, server, option_dict):
768 if self.server_supports_options_arg (server):
770 elif self.server_supports_call_id_arg (server):
771 return [ unique_call_id () ]
775 ### cis = call_id if supported - like ois
776 def cis (self, server):
777 if self.server_supports_call_id_arg (server):
778 return [ unique_call_id ]
782 ######################################## miscell utilities
783 def get_rspec_file(self, rspec):
784 if (os.path.isabs(rspec)):
787 file = os.path.join(self.options.sfi_dir, rspec)
788 if (os.path.isfile(file)):
791 self.logger.critical("No such rspec file %s"%rspec)
794 def get_record_file(self, record):
795 if (os.path.isabs(record)):
798 file = os.path.join(self.options.sfi_dir, record)
799 if (os.path.isfile(file)):
802 self.logger.critical("No such registry record file %s"%record)
806 #==========================================================================
807 # Following functions implement the commands
809 # Registry-related commands
810 #==========================================================================
812 @register_command("","")
813 def config (self, options, args):
814 "Display contents of current config"
815 print "# From configuration file %s"%self.config_file
816 flags=[ ('sfi', [ ('registry','reg_url'),
817 ('auth','authority'),
823 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
825 for (section, tuples) in flags:
828 for (external_name, internal_name) in tuples:
829 print "%-20s = %s"%(external_name,getattr(self,internal_name))
832 varname="%s_%s"%(section.upper(),name.upper())
833 value=getattr(self.config_instance,varname)
834 print "%-20s = %s"%(name,value)
836 @register_command("","")
837 def version(self, options, args):
839 display an SFA server version (GetVersion)
840 or version information about sfi itself
842 if options.version_local:
843 version=version_core()
845 if options.interface == "registry":
846 server=self.registry()
848 server = self.sliceapi()
849 result = server.GetVersion()
850 version = ReturnValue.get_value(result)
852 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
854 pprinter = PrettyPrinter(indent=4)
855 pprinter.pprint(version)
857 @register_command("authority","")
858 def list(self, options, args):
860 list entries in named authority registry (List)
867 if options.recursive:
868 opts['recursive'] = options.recursive
870 if options.show_credential:
871 show_credentials(self.my_credential_string)
873 list = self.registry().List(hrn, self.my_credential_string, options)
875 raise Exception, "Not enough parameters for the 'list' command"
877 # filter on person, slice, site, node, etc.
878 # This really should be in the self.filter_records funct def comment...
879 list = filter_records(options.type, list)
880 terminal_render (list, options)
882 save_records_to_file(options.file, list, options.fileformat)
885 @register_command("name","")
886 def show(self, options, args):
888 show details about named registry record (Resolve)
894 # explicitly require Resolve to run in details mode
895 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
896 record_dicts = filter_records(options.type, record_dicts)
898 self.logger.error("No record of type %s"% options.type)
900 # user has required to focus on some keys
902 def project (record):
904 for key in options.keys:
905 try: projected[key]=record[key]
908 record_dicts = [ project (record) for record in record_dicts ]
909 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
910 for record in records:
911 if (options.format == "text"): record.dump(sort=True)
912 else: print record.save_as_xml()
914 save_records_to_file(options.file, record_dicts, options.fileformat)
917 @register_command("[xml-filename]","")
918 def add(self, options, args):
919 """add record into registry (Register)
920 from command line options (recommended)
921 old-school method involving an xml file still supported"""
923 auth_cred = self.my_authority_credential_string()
924 if options.show_credential:
925 show_credentials(auth_cred)
932 record_filepath = args[0]
933 rec_file = self.get_record_file(record_filepath)
934 record_dict.update(load_record_from_file(rec_file).todict())
936 print "Cannot load record file %s"%record_filepath
939 record_dict.update(load_record_from_opts(options).todict())
940 # we should have a type by now
941 if 'type' not in record_dict :
944 # this is still planetlab dependent.. as plc will whine without that
945 # also, it's only for adding
946 if record_dict['type'] == 'user':
947 if not 'first_name' in record_dict:
948 record_dict['first_name'] = record_dict['hrn']
949 if 'last_name' not in record_dict:
950 record_dict['last_name'] = record_dict['hrn']
951 return self.registry().Register(record_dict, auth_cred)
953 @register_command("[xml-filename]","")
954 def update(self, options, args):
955 """update record into registry (Update)
956 from command line options (recommended)
957 old-school method involving an xml file still supported"""
960 record_filepath = args[0]
961 rec_file = self.get_record_file(record_filepath)
962 record_dict.update(load_record_from_file(rec_file).todict())
964 record_dict.update(load_record_from_opts(options).todict())
965 # at the very least we need 'type' here
966 if 'type' not in record_dict:
970 # don't translate into an object, as this would possibly distort
971 # user-provided data; e.g. add an 'email' field to Users
972 if record_dict['type'] == "user":
973 if record_dict['hrn'] == self.user:
974 cred = self.my_credential_string
976 cred = self.my_authority_credential_string()
977 elif record_dict['type'] in ["slice"]:
979 cred = self.slice_credential_string(record_dict['hrn'])
980 except ServerException, e:
981 # XXX smbaker -- once we have better error return codes, update this
982 # to do something better than a string compare
983 if "Permission error" in e.args[0]:
984 cred = self.my_authority_credential_string()
987 elif record_dict['type'] in ["authority"]:
988 cred = self.my_authority_credential_string()
989 elif record_dict['type'] == 'node':
990 cred = self.my_authority_credential_string()
992 raise "unknown record type" + record_dict['type']
993 if options.show_credential:
994 show_credentials(cred)
995 return self.registry().Update(record_dict, cred)
997 @register_command("hrn","")
998 def remove(self, options, args):
999 "remove registry record by name (Remove)"
1000 auth_cred = self.my_authority_credential_string()
1008 if options.show_credential:
1009 show_credentials(auth_cred)
1010 return self.registry().Remove(hrn, auth_cred, type)
1012 # ==================================================================
1013 # Slice-related commands
1014 # ==================================================================
1016 @register_command("","")
1017 def slices(self, options, args):
1018 "list instantiated slices (ListSlices) - returns urn's"
1019 server = self.sliceapi()
1021 creds = [self.my_credential_string]
1022 # options and call_id when supported
1024 api_options['call_id']=unique_call_id()
1025 if options.show_credential:
1026 show_credentials(creds)
1027 result = server.ListSlices(creds, *self.ois(server,api_options))
1028 value = ReturnValue.get_value(result)
1029 if self.options.raw:
1030 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1035 # show rspec for named slice
1036 @register_command("[slice_hrn]","")
1037 def resources(self, options, args):
1039 with no arg, discover available resources, (ListResources)
1040 or with an slice hrn, shows currently provisioned resources
1042 server = self.sliceapi()
1047 the_credential=self.slice_credential_string(args[0])
1048 creds.append(the_credential)
1050 the_credential=self.my_credential_string
1051 creds.append(the_credential)
1052 if options.show_credential:
1053 show_credentials(creds)
1055 # no need to check if server accepts the options argument since the options has
1056 # been a required argument since v1 API
1058 # always send call_id to v2 servers
1059 api_options ['call_id'] = unique_call_id()
1060 # ask for cached value if available
1061 api_options ['cached'] = True
1064 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
1066 api_options['info'] = options.info
1067 if options.list_leases:
1068 api_options['list_leases'] = options.list_leases
1070 if options.current == True:
1071 api_options['cached'] = False
1073 api_options['cached'] = True
1074 if options.rspec_version:
1075 version_manager = VersionManager()
1076 server_version = self.get_cached_server_version(server)
1077 if 'sfa' in server_version:
1078 # just request the version the client wants
1079 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1081 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1083 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1084 result = server.ListResources (creds, api_options)
1085 value = ReturnValue.get_value(result)
1086 if self.options.raw:
1087 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1088 if options.file is not None:
1089 save_rspec_to_file(value, options.file)
1090 if (self.options.raw is None) and (options.file is None):
1091 display_rspec(value, options.format)
1095 @register_command("slice_hrn rspec","")
1096 def create(self, options, args):
1098 create or update named slice with given rspec (CreateSliver)
1100 server = self.sliceapi()
1102 # xxx do we need to check usage (len(args)) ?
1105 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1108 creds = [self.slice_credential_string(slice_hrn)]
1110 delegated_cred = None
1111 server_version = self.get_cached_server_version(server)
1112 if server_version.get('interface') == 'slicemgr':
1113 # delegate our cred to the slice manager
1114 # do not delegate cred to slicemgr...not working at the moment
1116 #if server_version.get('hrn'):
1117 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1118 #elif server_version.get('urn'):
1119 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1121 if options.show_credential:
1122 show_credentials(creds)
1125 rspec_file = self.get_rspec_file(args[1])
1126 rspec = open(rspec_file).read()
1129 # need to pass along user keys to the aggregate.
1131 # { urn: urn:publicid:IDN+emulab.net+user+alice
1132 # keys: [<ssh key A>, <ssh key B>]
1135 # xxx Thierry 2012 sept. 21
1136 # contrary to what I was first thinking, calling Resolve with details=False does not yet work properly here
1137 # I am turning details=True on again on a - hopefully - temporary basis, just to get this whole thing to work again
1138 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1139 # slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string], {'details':True})
1140 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']:
1141 slice_record = slice_records[0]
1142 user_hrns = slice_record['reg-researchers']
1143 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1144 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1146 if 'sfa' not in server_version:
1147 users = pg_users_arg(user_records)
1148 #rspec = RSpec(rspec)
1149 #rspec.filter({'component_manager_id': server_version['urn']})
1150 #rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1152 users = sfa_users_arg(user_records, slice_record)
1154 # do not append users, keys, or slice tags. Anything
1155 # not contained in this request will be removed from the slice
1157 # CreateSliver has supported the options argument for a while now so it should
1158 # be safe to assume this server support it
1160 api_options ['append'] = False
1161 api_options ['call_id'] = unique_call_id()
1162 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1163 value = ReturnValue.get_value(result)
1164 if self.options.raw:
1165 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1166 if options.file is not None:
1167 save_rspec_to_file (value, options.file)
1168 if (self.options.raw is None) and (options.file is None):
1173 @register_command("slice_hrn","")
1174 def delete(self, options, args):
1176 delete named slice (DeleteSliver)
1178 server = self.sliceapi()
1182 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1185 slice_cred = self.slice_credential_string(slice_hrn)
1186 creds = [slice_cred]
1188 # options and call_id when supported
1190 api_options ['call_id'] = unique_call_id()
1191 if options.show_credential:
1192 show_credentials(creds)
1193 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1194 value = ReturnValue.get_value(result)
1195 if self.options.raw:
1196 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1201 @register_command("slice_hrn","")
1202 def status(self, options, args):
1204 retrieve slice status (SliverStatus)
1206 server = self.sliceapi()
1210 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1213 slice_cred = self.slice_credential_string(slice_hrn)
1214 creds = [slice_cred]
1216 # options and call_id when supported
1218 api_options['call_id']=unique_call_id()
1219 if options.show_credential:
1220 show_credentials(creds)
1221 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1222 value = ReturnValue.get_value(result)
1223 if self.options.raw:
1224 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1228 @register_command("slice_hrn","")
1229 def start(self, options, args):
1231 start named slice (Start)
1233 server = self.sliceapi()
1237 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1240 slice_cred = self.slice_credential_string(args[0])
1241 creds = [slice_cred]
1242 # xxx Thierry - does this not need an api_options as well ?
1243 result = server.Start(slice_urn, creds)
1244 value = ReturnValue.get_value(result)
1245 if self.options.raw:
1246 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1251 @register_command("slice_hrn","")
1252 def stop(self, options, args):
1254 stop named slice (Stop)
1256 server = self.sliceapi()
1259 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1261 slice_cred = self.slice_credential_string(args[0])
1262 creds = [slice_cred]
1263 result = server.Stop(slice_urn, creds)
1264 value = ReturnValue.get_value(result)
1265 if self.options.raw:
1266 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1272 @register_command("slice_hrn","")
1273 def reset(self, options, args):
1275 reset named slice (reset_slice)
1277 server = self.sliceapi()
1280 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1282 slice_cred = self.slice_credential_string(args[0])
1283 creds = [slice_cred]
1284 result = server.reset_slice(creds, slice_urn)
1285 value = ReturnValue.get_value(result)
1286 if self.options.raw:
1287 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1292 @register_command("slice_hrn time","")
1293 def renew(self, options, args):
1295 renew slice (RenewSliver)
1297 server = self.sliceapi()
1301 [ slice_hrn, input_time ] = args
1303 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1304 # time: don't try to be smart on the time format, server-side will
1306 slice_cred = self.slice_credential_string(args[0])
1307 creds = [slice_cred]
1308 # options and call_id when supported
1310 api_options['call_id']=unique_call_id()
1311 if options.show_credential:
1312 show_credentials(creds)
1313 result = server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
1314 value = ReturnValue.get_value(result)
1315 if self.options.raw:
1316 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1322 @register_command("slice_hrn","")
1323 def shutdown(self, options, args):
1325 shutdown named slice (Shutdown)
1327 server = self.sliceapi()
1330 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1332 slice_cred = self.slice_credential_string(slice_hrn)
1333 creds = [slice_cred]
1334 result = server.Shutdown(slice_urn, creds)
1335 value = ReturnValue.get_value(result)
1336 if self.options.raw:
1337 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1343 @register_command("slice_hrn rspec","")
1344 def get_ticket(self, options, args):
1346 get a ticket for the specified slice
1348 server = self.sliceapi()
1350 slice_hrn, rspec_path = args[0], args[1]
1351 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1353 slice_cred = self.slice_credential_string(slice_hrn)
1354 creds = [slice_cred]
1356 rspec_file = self.get_rspec_file(rspec_path)
1357 rspec = open(rspec_file).read()
1358 # options and call_id when supported
1360 api_options['call_id']=unique_call_id()
1361 # get ticket at the server
1362 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1364 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1365 self.logger.info("writing ticket to %s"%file)
1366 ticket = SfaTicket(string=ticket_string)
1367 ticket.save_to_file(filename=file, save_parents=True)
1369 @register_command("ticket","")
1370 def redeem_ticket(self, options, args):
1372 Connects to nodes in a slice and redeems a ticket
1373 (slice hrn is retrieved from the ticket)
1375 ticket_file = args[0]
1377 # get slice hrn from the ticket
1378 # use this to get the right slice credential
1379 ticket = SfaTicket(filename=ticket_file)
1381 ticket_string = ticket.save_to_string(save_parents=True)
1383 slice_hrn = ticket.gidObject.get_hrn()
1384 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1385 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1386 slice_cred = self.slice_credential_string(slice_hrn)
1388 # get a list of node hostnames from the RSpec
1389 tree = etree.parse(StringIO(ticket.rspec))
1390 root = tree.getroot()
1391 hostnames = root.xpath("./network/site/node/hostname/text()")
1393 # create an xmlrpc connection to the component manager at each of these
1394 # components and gall redeem_ticket
1396 for hostname in hostnames:
1398 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1399 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1400 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1401 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1402 timeout=self.options.timeout, verbose=self.options.debug)
1403 server.RedeemTicket(ticket_string, slice_cred)
1404 self.logger.info("Success")
1405 except socket.gaierror:
1406 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1407 except Exception, e:
1408 self.logger.log_exc(e.message)
1411 @register_command("[name]","")
1412 def gid(self, options, args):
1414 Create a GID (CreateGid)
1419 target_hrn = args[0]
1420 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1421 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1423 filename = options.file
1425 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1426 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1427 GID(string=gid).save_to_file(filename)
1429 ####################
1430 @register_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1432 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1433 the set of credentials in the scope for this call would be
1434 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1436 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1438 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1439 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1440 because of the two -s options
1443 def delegate (self, options, args):
1445 (locally) create delegate credential for use by given hrn
1446 make sure to check for 'sfi myslice' instead if you plan
1453 # support for several delegations in the same call
1454 # so first we gather the things to do
1456 for slice_hrn in options.delegate_slices:
1457 message="%s.slice"%slice_hrn
1458 original = self.slice_credential_string(slice_hrn)
1459 tuples.append ( (message, original,) )
1460 if options.delegate_pi:
1461 my_authority=self.authority
1462 message="%s.pi"%my_authority
1463 original = self.my_authority_credential_string()
1464 tuples.append ( (message, original,) )
1465 for auth_hrn in options.delegate_auths:
1466 message="%s.auth"%auth_hrn
1467 original=self.authority_credential_string(auth_hrn)
1468 tuples.append ( (message, original, ) )
1469 # if nothing was specified at all at this point, let's assume -u
1470 if not tuples: options.delegate_user=True
1472 if options.delegate_user:
1473 message="%s.user"%self.user
1474 original = self.my_credential_string
1475 tuples.append ( (message, original, ) )
1477 # default type for beneficial is user unless -A
1478 if options.delegate_to_authority: to_type='authority'
1479 else: to_type='user'
1481 # let's now handle all this
1482 # it's all in the filenaming scheme
1483 for (message,original) in tuples:
1484 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1485 delegated_credential = Credential (string=delegated_string)
1486 filename = os.path.join ( self.options.sfi_dir,
1487 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1488 delegated_credential.save_to_file(filename, save_parents=True)
1489 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1491 ####################
1492 @register_command("","""$ less +/myslice sfi_config
1494 backend = http://manifold.pl.sophia.inria.fr:7080
1495 # the HRN that myslice uses, so that we are delegating to
1496 delegate = ple.upmc.slicebrowser
1497 # platform - this is a myslice concept
1499 # username - as of this writing (May 2013) a simple login name
1503 will first collect the slices that you are part of, then make sure
1504 all your credentials are up-to-date (read: refresh expired ones)
1505 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1506 and upload them all on myslice backend, using 'platform' and 'user'.
1507 A password will be prompted for the upload part.
1509 $ sfi -v myslice -- or sfi -vv myslice
1510 same but with more and more verbosity
1512 $ sfi m -b http://mymanifold.foo.com:7080/
1513 is synonym to sfi myslice as no other command starts with an 'm'
1514 and uses a custom backend for this one call
1516 ) # register_command
1517 def myslice (self, options, args):
1519 """ This helper is for refreshing your credentials at myslice; it will
1520 * compute all the slices that you currently have credentials on
1521 * refresh all your credentials (you as a user and pi, your slices)
1522 * upload them to the manifold backend server
1523 for last phase, sfi_config is read to look for the [myslice] section,
1524 and namely the 'backend', 'delegate' and 'user' settings"""
1530 # enable info by default
1531 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1532 ### the rough sketch goes like this
1533 # (0) produce a p12 file
1534 self.client_bootstrap.my_pkcs12()
1536 # (a) rain check for sufficient config in sfi_config
1538 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1539 for key in myslice_keys:
1541 # oct 2013 - I'm finding myself juggling with config files
1542 # so a couple of command-line options can now override config
1543 if hasattr(options,key) and getattr(options,key) is not None:
1544 value=getattr(options,key)
1546 full_key="MYSLICE_" + key.upper()
1547 value=getattr(self.config_instance,full_key,None)
1548 if value: myslice_dict[key]=value
1549 else: print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1550 if len(myslice_dict) != len(myslice_keys):
1553 # (b) figure whether we are PI for the authority where we belong
1554 self.logger.info("Resolving our own id %s"%self.user)
1555 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1556 if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1557 my_record=my_records[0]
1558 my_auths_all = my_record['reg-pi-authorities']
1559 self.logger.info("Found %d authorities that we are PI for"%len(my_auths_all))
1560 self.logger.debug("They are %s"%(my_auths_all))
1562 my_auths = my_auths_all
1563 if options.delegate_auths:
1564 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1565 self.logger.debug("Restricted to user-provided auths"%(my_auths))
1567 # (c) get the set of slices that we are in
1568 my_slices_all=my_record['reg-slices']
1569 self.logger.info("Found %d slices that we are member of"%len(my_slices_all))
1570 self.logger.debug("They are: %s"%(my_slices_all))
1572 my_slices = my_slices_all
1573 # if user provided slices, deal only with these - if they are found
1574 if options.delegate_slices:
1575 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1576 self.logger.debug("Restricted to user-provided slices: %s"%(my_slices))
1578 # (d) make sure we have *valid* credentials for all these
1580 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1581 for auth_hrn in my_auths:
1582 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1583 for slice_hrn in my_slices:
1584 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1586 # (e) check for the delegated version of these
1587 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1588 # switch to myslice using an authority instead of a user
1589 delegatee_type='user'
1590 delegatee_hrn=myslice_dict['delegate']
1591 hrn_delegated_credentials = []
1592 for (hrn, htype, credential) in hrn_credentials:
1593 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1594 # save these so user can monitor what she's uploaded
1595 filename = os.path.join ( self.options.sfi_dir,
1596 "%s.%s_for_%s.%s.cred"%(hrn,htype,delegatee_hrn,delegatee_type))
1597 with file(filename,'w') as f:
1598 f.write(delegated_credential)
1599 self.logger.debug("(Over)wrote %s"%filename)
1600 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1602 # (f) and finally upload them to manifold server
1603 # xxx todo add an option so the password can be set on the command line
1604 # (but *NOT* in the config file) so other apps can leverage this
1605 self.logger.info("Uploading on backend at %s"%myslice_dict['backend'])
1606 uploader = ManifoldUploader (logger=self.logger,
1607 url=myslice_dict['backend'],
1608 platform=myslice_dict['platform'],
1609 username=myslice_dict['username'],
1610 password=options.password)
1611 uploader.prompt_all()
1612 (count_all,count_success)=(0,0)
1613 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1615 inspect=Credential(string=delegated_credential)
1616 expire_datetime=inspect.get_expiration()
1617 message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
1618 if uploader.upload(delegated_credential,message=message):
1621 self.logger.info("Successfully uploaded %d/%d credentials"%(count_success,count_all))
1623 # at first I thought we would want to save these,
1624 # like 'sfi delegate does' but on second thought
1625 # it is probably not helpful as people would not
1626 # need to run 'sfi delegate' at all anymore
1627 if count_success != count_all: sys.exit(1)
1630 @register_command("cred","")
1631 def trusted(self, options, args):
1633 return the trusted certs at this interface (get_trusted_certs)
1635 if options.interface == "registry":
1636 server=self.registry()
1638 server = self.sliceapi()
1639 cred = self.my_authority_credential_string()
1640 trusted_certs = server.get_trusted_certs(cred)
1641 if options.interface != "registry":
1642 trusted_certs = ReturnValue.get_value(trusted_certs)
1644 for trusted_cert in trusted_certs:
1645 print "\n===========================================================\n"
1646 gid = GID(string=trusted_cert)
1648 cert = Certificate(string=trusted_cert)
1649 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1650 print "Certificate:\n%s\n\n"%trusted_cert