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("-R","--registry-version",
383 action="store_true", dest="version_registry", default=False,
384 help="probe registry version instead of sliceapi")
385 parser.add_option("-l","--local",
386 action="store_true", dest="version_local", default=False,
387 help="display version of the local client")
389 if command in ("trusted"):
390 parser.add_option("-R","--registry-version",
391 action="store_true", dest="version_registry", default=False,
392 help="probe registry version instead of sliceapi")
394 if command in ("add", "update"):
395 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
396 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
397 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
398 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
400 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
401 default='', type="str", action='callback', callback=optparse_listvalue_callback)
402 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
403 help='Set/replace slice researchers', default='', type="str", action='callback',
404 callback=optparse_listvalue_callback)
405 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
406 default='', type="str", action='callback', callback=optparse_listvalue_callback)
407 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
408 action="callback", callback=optparse_dictvalue_callback, nargs=1,
409 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
411 # show_credential option
412 if command in ("list","resources","create","add","update","remove","slices","delete","status","renew"):
413 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
414 help="show credential(s) used in human-readable form")
415 # registy filter option
416 if command in ("list", "show", "remove"):
417 parser.add_option("-t", "--type", dest="type", type="choice",
418 help="type filter ([all]|user|slice|authority|node|aggregate)",
419 choices=("all", "user", "slice", "authority", "node", "aggregate"),
421 if command in ("show"):
422 parser.add_option("-k","--key",dest="keys",action="append",default=[],
423 help="specify specific keys to be displayed from record")
424 if command in ("resources"):
426 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
427 help="schema type and version of resulting RSpec")
428 # disable/enable cached rspecs
429 parser.add_option("-c", "--current", dest="current", default=False,
431 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
433 parser.add_option("-f", "--format", dest="format", type="choice",
434 help="display format ([xml]|dns|ip)", default="xml",
435 choices=("xml", "dns", "ip"))
436 #panos: a new option to define the type of information about resources a user is interested in
437 parser.add_option("-i", "--info", dest="info",
438 help="optional component information", default=None)
439 # a new option to retreive or not reservation-oriented RSpecs (leases)
440 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
441 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
442 choices=("all", "resources", "leases"), default="resources")
445 # 'create' does return the new rspec, makes sense to save that too
446 if command in ("resources", "show", "list", "gid", 'create'):
447 parser.add_option("-o", "--output", dest="file",
448 help="output XML to file", metavar="FILE", default=None)
450 if command in ("show", "list"):
451 parser.add_option("-f", "--format", dest="format", type="choice",
452 help="display format ([text]|xml)", default="text",
453 choices=("text", "xml"))
455 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
456 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
457 choices=("xml", "xmllist", "hrnlist"))
458 if command == 'list':
459 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
460 help="list all child records", default=False)
461 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
462 help="gives details, like user keys", default=False)
463 if command in ("delegate"):
464 parser.add_option("-u", "--user",
465 action="store_true", dest="delegate_user", default=False,
466 help="delegate your own credentials; default if no other option is provided")
467 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
468 metavar="slice_hrn", help="delegate cred. for slice HRN")
469 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
470 metavar='auth_hrn', help="delegate cred for auth HRN")
471 # this primarily is a shorthand for -a my_hrn
472 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
473 help="delegate your PI credentials, so s.t. like -a your_hrn^")
474 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
475 help="""by default the mandatory argument is expected to be a user,
476 use this if you mean an authority instead""")
478 if command in ("myslice"):
479 parser.add_option("-p","--password",dest='password',action='store',default=None,
480 help="specify mainfold password on the command line")
481 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
482 metavar="slice_hrn", help="delegate cred. for slice HRN")
483 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
484 metavar='auth_hrn', help="delegate PI cred for auth HRN")
485 parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
486 parser.add_option('-b', '--backend', dest='backend', help="Override 'backend' from the config file")
492 # Main: parse arguments and dispatch to command
494 def dispatch(self, command, command_options, command_args):
495 method=getattr(self, command, None)
497 print "Unknown command %s"%command
499 return method(command_options, command_args)
502 self.sfi_parser = self.create_parser_global()
503 (options, args) = self.sfi_parser.parse_args()
505 self.print_commands_help(options)
507 self.options = options
509 self.logger.setLevelFromOptVerbose(self.options.verbose)
512 self.logger.critical("No command given. Use -h for help.")
513 self.print_commands_help(options)
516 # complete / find unique match with command set
517 command_candidates = Candidates (commands_list)
519 command = command_candidates.only_match(input)
521 self.print_commands_help(options)
523 # second pass options parsing
525 self.command_parser = self.create_parser_command(command)
526 (command_options, command_args) = self.command_parser.parse_args(args[1:])
527 if command_options.help:
530 self.command_options = command_options
534 self.logger.debug("Command=%s" % self.command)
537 self.dispatch(command, command_options, command_args)
541 self.logger.log_exc ("sfi command %s failed"%command)
547 def read_config(self):
548 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
549 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
551 if Config.is_ini(config_file):
552 config = Config (config_file)
554 # try upgrading from shell config format
555 fp, fn = mkstemp(suffix='sfi_config', text=True)
557 # we need to preload the sections we want parsed
558 # from the shell config
559 config.add_section('sfi')
560 # sface users should be able to use this same file to configure their stuff
561 config.add_section('sface')
562 # manifold users should be able to specify the details
563 # of their backend server here for 'sfi myslice'
564 config.add_section('myslice')
565 config.load(config_file)
567 shutil.move(config_file, shell_config_file)
569 config.save(config_file)
572 self.logger.critical("Failed to read configuration file %s"%config_file)
573 self.logger.info("Make sure to remove the export clauses and to add quotes")
574 if self.options.verbose==0:
575 self.logger.info("Re-run with -v for more details")
577 self.logger.log_exc("Could not read config file %s"%config_file)
580 self.config_instance=config
583 if (self.options.sm is not None):
584 self.sm_url = self.options.sm
585 elif hasattr(config, "SFI_SM"):
586 self.sm_url = config.SFI_SM
588 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
592 if (self.options.registry is not None):
593 self.reg_url = self.options.registry
594 elif hasattr(config, "SFI_REGISTRY"):
595 self.reg_url = config.SFI_REGISTRY
597 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
601 if (self.options.user is not None):
602 self.user = self.options.user
603 elif hasattr(config, "SFI_USER"):
604 self.user = config.SFI_USER
606 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
610 if (self.options.auth is not None):
611 self.authority = self.options.auth
612 elif hasattr(config, "SFI_AUTH"):
613 self.authority = config.SFI_AUTH
615 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
618 self.config_file=config_file
623 # Get various credential and spec files
625 # Establishes limiting conventions
626 # - conflates MAs and SAs
627 # - assumes last token in slice name is unique
629 # Bootstraps credentials
630 # - bootstrap user credential from self-signed certificate
631 # - bootstrap authority credential from user credential
632 # - bootstrap slice credential from user credential
635 # init self-signed cert, user credentials and gid
636 def bootstrap (self):
637 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
639 # if -k is provided, use this to initialize private key
640 if self.options.user_private_key:
641 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
643 # trigger legacy compat code if needed
644 # the name has changed from just <leaf>.pkey to <hrn>.pkey
645 if not os.path.isfile(client_bootstrap.private_key_filename()):
646 self.logger.info ("private key not found, trying legacy name")
648 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
649 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
650 client_bootstrap.init_private_key_if_missing (legacy_private_key)
651 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
653 self.logger.log_exc("Can't find private key ")
657 client_bootstrap.bootstrap_my_gid()
658 # extract what's needed
659 self.private_key = client_bootstrap.private_key()
660 self.my_credential_string = client_bootstrap.my_credential_string ()
661 self.my_gid = client_bootstrap.my_gid ()
662 self.client_bootstrap = client_bootstrap
665 def my_authority_credential_string(self):
666 if not self.authority:
667 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
669 return self.client_bootstrap.authority_credential_string (self.authority)
671 def authority_credential_string(self, auth_hrn):
672 return self.client_bootstrap.authority_credential_string (auth_hrn)
674 def slice_credential_string(self, name):
675 return self.client_bootstrap.slice_credential_string (name)
678 # Management of the servers
683 if not hasattr (self, 'registry_proxy'):
684 self.logger.info("Contacting Registry at: %s"%self.reg_url)
685 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
686 timeout=self.options.timeout, verbose=self.options.debug)
687 return self.registry_proxy
691 if not hasattr (self, 'sliceapi_proxy'):
692 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
693 if hasattr(self.command_options,'component') and self.command_options.component:
694 # resolve the hrn at the registry
695 node_hrn = self.command_options.component
696 records = self.registry().Resolve(node_hrn, self.my_credential_string)
697 records = filter_records('node', records)
699 self.logger.warning("No such component:%r"% opts.component)
701 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
702 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
704 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
705 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
706 self.sm_url = 'http://' + self.sm_url
707 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
708 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
709 timeout=self.options.timeout, verbose=self.options.debug)
710 return self.sliceapi_proxy
712 def get_cached_server_version(self, server):
713 # check local cache first
716 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
717 cache_key = server.url + "-version"
719 cache = Cache(cache_file)
722 self.logger.info("Local cache not found at: %s" % cache_file)
725 version = cache.get(cache_key)
728 result = server.GetVersion()
729 version= ReturnValue.get_value(result)
730 # cache version for 20 minutes
731 cache.add(cache_key, version, ttl= 60*20)
732 self.logger.info("Updating cache file %s" % cache_file)
733 cache.save_to_file(cache_file)
737 ### resurrect this temporarily so we can support V1 aggregates for a while
738 def server_supports_options_arg(self, server):
740 Returns true if server support the optional call_id arg, false otherwise.
742 server_version = self.get_cached_server_version(server)
744 # xxx need to rewrite this
745 if int(server_version.get('geni_api')) >= 2:
749 def server_supports_call_id_arg(self, server):
750 server_version = self.get_cached_server_version(server)
752 if 'sfa' in server_version and 'code_tag' in server_version:
753 code_tag = server_version['code_tag']
754 code_tag_parts = code_tag.split("-")
755 version_parts = code_tag_parts[0].split(".")
756 major, minor = version_parts[0], version_parts[1]
757 rev = code_tag_parts[1]
758 if int(major) == 1 and minor == 0 and build >= 22:
762 ### ois = options if supported
763 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
764 def ois (self, server, option_dict):
765 if self.server_supports_options_arg (server):
767 elif self.server_supports_call_id_arg (server):
768 return [ unique_call_id () ]
772 ### cis = call_id if supported - like ois
773 def cis (self, server):
774 if self.server_supports_call_id_arg (server):
775 return [ unique_call_id ]
779 ######################################## miscell utilities
780 def get_rspec_file(self, rspec):
781 if (os.path.isabs(rspec)):
784 file = os.path.join(self.options.sfi_dir, rspec)
785 if (os.path.isfile(file)):
788 self.logger.critical("No such rspec file %s"%rspec)
791 def get_record_file(self, record):
792 if (os.path.isabs(record)):
795 file = os.path.join(self.options.sfi_dir, record)
796 if (os.path.isfile(file)):
799 self.logger.critical("No such registry record file %s"%record)
803 #==========================================================================
804 # Following functions implement the commands
806 # Registry-related commands
807 #==========================================================================
809 @register_command("","")
810 def config (self, options, args):
811 "Display contents of current config"
812 print "# From configuration file %s"%self.config_file
813 flags=[ ('sfi', [ ('registry','reg_url'),
814 ('auth','authority'),
820 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
822 for (section, tuples) in flags:
825 for (external_name, internal_name) in tuples:
826 print "%-20s = %s"%(external_name,getattr(self,internal_name))
829 varname="%s_%s"%(section.upper(),name.upper())
830 value=getattr(self.config_instance,varname)
831 print "%-20s = %s"%(name,value)
833 @register_command("","")
834 def version(self, options, args):
836 display an SFA server version (GetVersion)
837 or version information about sfi itself
839 if options.version_local:
840 version=version_core()
842 if options.version_registry:
843 server=self.registry()
845 server = self.sliceapi()
846 result = server.GetVersion()
847 version = ReturnValue.get_value(result)
849 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
851 pprinter = PrettyPrinter(indent=4)
852 pprinter.pprint(version)
854 @register_command("authority","")
855 def list(self, options, args):
857 list entries in named authority registry (List)
864 if options.recursive:
865 opts['recursive'] = options.recursive
867 if options.show_credential:
868 show_credentials(self.my_credential_string)
870 list = self.registry().List(hrn, self.my_credential_string, options)
872 raise Exception, "Not enough parameters for the 'list' command"
874 # filter on person, slice, site, node, etc.
875 # This really should be in the self.filter_records funct def comment...
876 list = filter_records(options.type, list)
877 terminal_render (list, options)
879 save_records_to_file(options.file, list, options.fileformat)
882 @register_command("name","")
883 def show(self, options, args):
885 show details about named registry record (Resolve)
891 # explicitly require Resolve to run in details mode
892 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
893 record_dicts = filter_records(options.type, record_dicts)
895 self.logger.error("No record of type %s"% options.type)
897 # user has required to focus on some keys
899 def project (record):
901 for key in options.keys:
902 try: projected[key]=record[key]
905 record_dicts = [ project (record) for record in record_dicts ]
906 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
907 for record in records:
908 if (options.format == "text"): record.dump(sort=True)
909 else: print record.save_as_xml()
911 save_records_to_file(options.file, record_dicts, options.fileformat)
914 @register_command("[xml-filename]","")
915 def add(self, options, args):
916 """add record into registry (Register)
917 from command line options (recommended)
918 old-school method involving an xml file still supported"""
920 auth_cred = self.my_authority_credential_string()
921 if options.show_credential:
922 show_credentials(auth_cred)
929 record_filepath = args[0]
930 rec_file = self.get_record_file(record_filepath)
931 record_dict.update(load_record_from_file(rec_file).todict())
933 print "Cannot load record file %s"%record_filepath
936 record_dict.update(load_record_from_opts(options).todict())
937 # we should have a type by now
938 if 'type' not in record_dict :
941 # this is still planetlab dependent.. as plc will whine without that
942 # also, it's only for adding
943 if record_dict['type'] == 'user':
944 if not 'first_name' in record_dict:
945 record_dict['first_name'] = record_dict['hrn']
946 if 'last_name' not in record_dict:
947 record_dict['last_name'] = record_dict['hrn']
948 return self.registry().Register(record_dict, auth_cred)
950 @register_command("[xml-filename]","")
951 def update(self, options, args):
952 """update record into registry (Update)
953 from command line options (recommended)
954 old-school method involving an xml file still supported"""
957 record_filepath = args[0]
958 rec_file = self.get_record_file(record_filepath)
959 record_dict.update(load_record_from_file(rec_file).todict())
961 record_dict.update(load_record_from_opts(options).todict())
962 # at the very least we need 'type' here
963 if 'type' not in record_dict:
967 # don't translate into an object, as this would possibly distort
968 # user-provided data; e.g. add an 'email' field to Users
969 if record_dict['type'] == "user":
970 if record_dict['hrn'] == self.user:
971 cred = self.my_credential_string
973 cred = self.my_authority_credential_string()
974 elif record_dict['type'] in ["slice"]:
976 cred = self.slice_credential_string(record_dict['hrn'])
977 except ServerException, e:
978 # XXX smbaker -- once we have better error return codes, update this
979 # to do something better than a string compare
980 if "Permission error" in e.args[0]:
981 cred = self.my_authority_credential_string()
984 elif record_dict['type'] in ["authority"]:
985 cred = self.my_authority_credential_string()
986 elif record_dict['type'] == 'node':
987 cred = self.my_authority_credential_string()
989 raise "unknown record type" + record_dict['type']
990 if options.show_credential:
991 show_credentials(cred)
992 return self.registry().Update(record_dict, cred)
994 @register_command("hrn","")
995 def remove(self, options, args):
996 "remove registry record by name (Remove)"
997 auth_cred = self.my_authority_credential_string()
1005 if options.show_credential:
1006 show_credentials(auth_cred)
1007 return self.registry().Remove(hrn, auth_cred, type)
1009 # ==================================================================
1010 # Slice-related commands
1011 # ==================================================================
1013 @register_command("","")
1014 def slices(self, options, args):
1015 "list instantiated slices (ListSlices) - returns urn's"
1016 server = self.sliceapi()
1018 creds = [self.my_credential_string]
1019 # options and call_id when supported
1021 api_options['call_id']=unique_call_id()
1022 if options.show_credential:
1023 show_credentials(creds)
1024 result = server.ListSlices(creds, *self.ois(server,api_options))
1025 value = ReturnValue.get_value(result)
1026 if self.options.raw:
1027 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1032 # show rspec for named slice
1033 @register_command("[slice_hrn]","")
1034 def resources(self, options, args):
1036 with no arg, discover available resources, (ListResources)
1037 or with an slice hrn, shows currently provisioned resources
1039 server = self.sliceapi()
1044 the_credential=self.slice_credential_string(args[0])
1045 creds.append(the_credential)
1047 the_credential=self.my_credential_string
1048 creds.append(the_credential)
1049 if options.show_credential:
1050 show_credentials(creds)
1052 # no need to check if server accepts the options argument since the options has
1053 # been a required argument since v1 API
1055 # always send call_id to v2 servers
1056 api_options ['call_id'] = unique_call_id()
1057 # ask for cached value if available
1058 api_options ['cached'] = True
1061 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
1063 api_options['info'] = options.info
1064 if options.list_leases:
1065 api_options['list_leases'] = options.list_leases
1067 if options.current == True:
1068 api_options['cached'] = False
1070 api_options['cached'] = True
1071 if options.rspec_version:
1072 version_manager = VersionManager()
1073 server_version = self.get_cached_server_version(server)
1074 if 'sfa' in server_version:
1075 # just request the version the client wants
1076 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1078 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1080 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1081 result = server.ListResources (creds, api_options)
1082 value = ReturnValue.get_value(result)
1083 if self.options.raw:
1084 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1085 if options.file is not None:
1086 save_rspec_to_file(value, options.file)
1087 if (self.options.raw is None) and (options.file is None):
1088 display_rspec(value, options.format)
1092 @register_command("slice_hrn rspec","")
1093 def create(self, options, args):
1095 create or update named slice with given rspec (CreateSliver)
1097 server = self.sliceapi()
1099 # xxx do we need to check usage (len(args)) ?
1102 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1105 creds = [self.slice_credential_string(slice_hrn)]
1107 delegated_cred = None
1108 server_version = self.get_cached_server_version(server)
1109 if server_version.get('interface') == 'slicemgr':
1110 # delegate our cred to the slice manager
1111 # do not delegate cred to slicemgr...not working at the moment
1113 #if server_version.get('hrn'):
1114 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1115 #elif server_version.get('urn'):
1116 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1118 if options.show_credential:
1119 show_credentials(creds)
1122 rspec_file = self.get_rspec_file(args[1])
1123 rspec = open(rspec_file).read()
1126 # need to pass along user keys to the aggregate.
1128 # { urn: urn:publicid:IDN+emulab.net+user+alice
1129 # keys: [<ssh key A>, <ssh key B>]
1132 # xxx Thierry 2012 sept. 21
1133 # contrary to what I was first thinking, calling Resolve with details=False does not yet work properly here
1134 # I am turning details=True on again on a - hopefully - temporary basis, just to get this whole thing to work again
1135 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1136 # slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string], {'details':True})
1137 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']:
1138 slice_record = slice_records[0]
1139 user_hrns = slice_record['reg-researchers']
1140 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1141 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1143 if 'sfa' not in server_version:
1144 users = pg_users_arg(user_records)
1145 #rspec = RSpec(rspec)
1146 #rspec.filter({'component_manager_id': server_version['urn']})
1147 #rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1149 users = sfa_users_arg(user_records, slice_record)
1151 # do not append users, keys, or slice tags. Anything
1152 # not contained in this request will be removed from the slice
1154 # CreateSliver has supported the options argument for a while now so it should
1155 # be safe to assume this server support it
1157 api_options ['append'] = False
1158 api_options ['call_id'] = unique_call_id()
1159 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1160 value = ReturnValue.get_value(result)
1161 if self.options.raw:
1162 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1163 if options.file is not None:
1164 save_rspec_to_file (value, options.file)
1165 if (self.options.raw is None) and (options.file is None):
1170 @register_command("slice_hrn","")
1171 def delete(self, options, args):
1173 delete named slice (DeleteSliver)
1175 server = self.sliceapi()
1179 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1182 slice_cred = self.slice_credential_string(slice_hrn)
1183 creds = [slice_cred]
1185 # options and call_id when supported
1187 api_options ['call_id'] = unique_call_id()
1188 if options.show_credential:
1189 show_credentials(creds)
1190 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1191 value = ReturnValue.get_value(result)
1192 if self.options.raw:
1193 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1198 @register_command("slice_hrn","")
1199 def status(self, options, args):
1201 retrieve slice status (SliverStatus)
1203 server = self.sliceapi()
1207 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1210 slice_cred = self.slice_credential_string(slice_hrn)
1211 creds = [slice_cred]
1213 # options and call_id when supported
1215 api_options['call_id']=unique_call_id()
1216 if options.show_credential:
1217 show_credentials(creds)
1218 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1219 value = ReturnValue.get_value(result)
1220 if self.options.raw:
1221 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1225 @register_command("slice_hrn","")
1226 def start(self, options, args):
1228 start named slice (Start)
1230 server = self.sliceapi()
1234 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1237 slice_cred = self.slice_credential_string(args[0])
1238 creds = [slice_cred]
1239 # xxx Thierry - does this not need an api_options as well ?
1240 result = server.Start(slice_urn, creds)
1241 value = ReturnValue.get_value(result)
1242 if self.options.raw:
1243 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1248 @register_command("slice_hrn","")
1249 def stop(self, options, args):
1251 stop named slice (Stop)
1253 server = self.sliceapi()
1256 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1258 slice_cred = self.slice_credential_string(args[0])
1259 creds = [slice_cred]
1260 result = server.Stop(slice_urn, creds)
1261 value = ReturnValue.get_value(result)
1262 if self.options.raw:
1263 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1269 @register_command("slice_hrn","")
1270 def reset(self, options, args):
1272 reset named slice (reset_slice)
1274 server = self.sliceapi()
1277 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1279 slice_cred = self.slice_credential_string(args[0])
1280 creds = [slice_cred]
1281 result = server.reset_slice(creds, slice_urn)
1282 value = ReturnValue.get_value(result)
1283 if self.options.raw:
1284 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1289 @register_command("slice_hrn time","")
1290 def renew(self, options, args):
1292 renew slice (RenewSliver)
1294 server = self.sliceapi()
1298 [ slice_hrn, input_time ] = args
1300 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1301 # time: don't try to be smart on the time format, server-side will
1303 slice_cred = self.slice_credential_string(args[0])
1304 creds = [slice_cred]
1305 # options and call_id when supported
1307 api_options['call_id']=unique_call_id()
1308 if options.show_credential:
1309 show_credentials(creds)
1310 result = server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
1311 value = ReturnValue.get_value(result)
1312 if self.options.raw:
1313 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1319 @register_command("slice_hrn","")
1320 def shutdown(self, options, args):
1322 shutdown named slice (Shutdown)
1324 server = self.sliceapi()
1327 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1329 slice_cred = self.slice_credential_string(slice_hrn)
1330 creds = [slice_cred]
1331 result = server.Shutdown(slice_urn, creds)
1332 value = ReturnValue.get_value(result)
1333 if self.options.raw:
1334 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1340 @register_command("slice_hrn rspec","")
1341 def get_ticket(self, options, args):
1343 get a ticket for the specified slice
1345 server = self.sliceapi()
1347 slice_hrn, rspec_path = args[0], args[1]
1348 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1350 slice_cred = self.slice_credential_string(slice_hrn)
1351 creds = [slice_cred]
1353 rspec_file = self.get_rspec_file(rspec_path)
1354 rspec = open(rspec_file).read()
1355 # options and call_id when supported
1357 api_options['call_id']=unique_call_id()
1358 # get ticket at the server
1359 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1361 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1362 self.logger.info("writing ticket to %s"%file)
1363 ticket = SfaTicket(string=ticket_string)
1364 ticket.save_to_file(filename=file, save_parents=True)
1366 @register_command("ticket","")
1367 def redeem_ticket(self, options, args):
1369 Connects to nodes in a slice and redeems a ticket
1370 (slice hrn is retrieved from the ticket)
1372 ticket_file = args[0]
1374 # get slice hrn from the ticket
1375 # use this to get the right slice credential
1376 ticket = SfaTicket(filename=ticket_file)
1378 ticket_string = ticket.save_to_string(save_parents=True)
1380 slice_hrn = ticket.gidObject.get_hrn()
1381 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1382 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1383 slice_cred = self.slice_credential_string(slice_hrn)
1385 # get a list of node hostnames from the RSpec
1386 tree = etree.parse(StringIO(ticket.rspec))
1387 root = tree.getroot()
1388 hostnames = root.xpath("./network/site/node/hostname/text()")
1390 # create an xmlrpc connection to the component manager at each of these
1391 # components and gall redeem_ticket
1393 for hostname in hostnames:
1395 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1396 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1397 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1398 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1399 timeout=self.options.timeout, verbose=self.options.debug)
1400 server.RedeemTicket(ticket_string, slice_cred)
1401 self.logger.info("Success")
1402 except socket.gaierror:
1403 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1404 except Exception, e:
1405 self.logger.log_exc(e.message)
1408 @register_command("[name]","")
1409 def gid(self, options, args):
1411 Create a GID (CreateGid)
1416 target_hrn = args[0]
1417 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1418 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1420 filename = options.file
1422 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1423 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1424 GID(string=gid).save_to_file(filename)
1426 ####################
1427 @register_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1429 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1430 the set of credentials in the scope for this call would be
1431 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1433 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1435 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1436 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1437 because of the two -s options
1440 def delegate (self, options, args):
1442 (locally) create delegate credential for use by given hrn
1443 make sure to check for 'sfi myslice' instead if you plan
1450 # support for several delegations in the same call
1451 # so first we gather the things to do
1453 for slice_hrn in options.delegate_slices:
1454 message="%s.slice"%slice_hrn
1455 original = self.slice_credential_string(slice_hrn)
1456 tuples.append ( (message, original,) )
1457 if options.delegate_pi:
1458 my_authority=self.authority
1459 message="%s.pi"%my_authority
1460 original = self.my_authority_credential_string()
1461 tuples.append ( (message, original,) )
1462 for auth_hrn in options.delegate_auths:
1463 message="%s.auth"%auth_hrn
1464 original=self.authority_credential_string(auth_hrn)
1465 tuples.append ( (message, original, ) )
1466 # if nothing was specified at all at this point, let's assume -u
1467 if not tuples: options.delegate_user=True
1469 if options.delegate_user:
1470 message="%s.user"%self.user
1471 original = self.my_credential_string
1472 tuples.append ( (message, original, ) )
1474 # default type for beneficial is user unless -A
1475 if options.delegate_to_authority: to_type='authority'
1476 else: to_type='user'
1478 # let's now handle all this
1479 # it's all in the filenaming scheme
1480 for (message,original) in tuples:
1481 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1482 delegated_credential = Credential (string=delegated_string)
1483 filename = os.path.join ( self.options.sfi_dir,
1484 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1485 delegated_credential.save_to_file(filename, save_parents=True)
1486 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1488 ####################
1489 @register_command("","""$ less +/myslice sfi_config
1491 backend = http://manifold.pl.sophia.inria.fr:7080
1492 # the HRN that myslice uses, so that we are delegating to
1493 delegate = ple.upmc.slicebrowser
1494 # platform - this is a myslice concept
1496 # username - as of this writing (May 2013) a simple login name
1500 will first collect the slices that you are part of, then make sure
1501 all your credentials are up-to-date (read: refresh expired ones)
1502 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1503 and upload them all on myslice backend, using 'platform' and 'user'.
1504 A password will be prompted for the upload part.
1506 $ sfi -v myslice -- or sfi -vv myslice
1507 same but with more and more verbosity
1509 $ sfi m -b http://mymanifold.foo.com:7080/
1510 is synonym to sfi myslice as no other command starts with an 'm'
1511 and uses a custom backend for this one call
1513 ) # register_command
1514 def myslice (self, options, args):
1516 """ This helper is for refreshing your credentials at myslice; it will
1517 * compute all the slices that you currently have credentials on
1518 * refresh all your credentials (you as a user and pi, your slices)
1519 * upload them to the manifold backend server
1520 for last phase, sfi_config is read to look for the [myslice] section,
1521 and namely the 'backend', 'delegate' and 'user' settings"""
1527 # enable info by default
1528 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1529 ### the rough sketch goes like this
1530 # (0) produce a p12 file
1531 self.client_bootstrap.my_pkcs12()
1533 # (a) rain check for sufficient config in sfi_config
1535 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1536 for key in myslice_keys:
1538 # oct 2013 - I'm finding myself juggling with config files
1539 # so a couple of command-line options can now override config
1540 if hasattr(options,key) and getattr(options,key) is not None:
1541 value=getattr(options,key)
1543 full_key="MYSLICE_" + key.upper()
1544 value=getattr(self.config_instance,full_key,None)
1545 if value: myslice_dict[key]=value
1546 else: print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1547 if len(myslice_dict) != len(myslice_keys):
1550 # (b) figure whether we are PI for the authority where we belong
1551 self.logger.info("Resolving our own id %s"%self.user)
1552 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1553 if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1554 my_record=my_records[0]
1555 my_auths_all = my_record['reg-pi-authorities']
1556 self.logger.info("Found %d authorities that we are PI for"%len(my_auths_all))
1557 self.logger.debug("They are %s"%(my_auths_all))
1559 my_auths = my_auths_all
1560 if options.delegate_auths:
1561 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1562 self.logger.debug("Restricted to user-provided auths"%(my_auths))
1564 # (c) get the set of slices that we are in
1565 my_slices_all=my_record['reg-slices']
1566 self.logger.info("Found %d slices that we are member of"%len(my_slices_all))
1567 self.logger.debug("They are: %s"%(my_slices_all))
1569 my_slices = my_slices_all
1570 # if user provided slices, deal only with these - if they are found
1571 if options.delegate_slices:
1572 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1573 self.logger.debug("Restricted to user-provided slices: %s"%(my_slices))
1575 # (d) make sure we have *valid* credentials for all these
1577 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1578 for auth_hrn in my_auths:
1579 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1580 for slice_hrn in my_slices:
1581 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1583 # (e) check for the delegated version of these
1584 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1585 # switch to myslice using an authority instead of a user
1586 delegatee_type='user'
1587 delegatee_hrn=myslice_dict['delegate']
1588 hrn_delegated_credentials = []
1589 for (hrn, htype, credential) in hrn_credentials:
1590 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1591 # save these so user can monitor what she's uploaded
1592 filename = os.path.join ( self.options.sfi_dir,
1593 "%s.%s_for_%s.%s.cred"%(hrn,htype,delegatee_hrn,delegatee_type))
1594 with file(filename,'w') as f:
1595 f.write(delegated_credential)
1596 self.logger.debug("(Over)wrote %s"%filename)
1597 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1599 # (f) and finally upload them to manifold server
1600 # xxx todo add an option so the password can be set on the command line
1601 # (but *NOT* in the config file) so other apps can leverage this
1602 self.logger.info("Uploading on backend at %s"%myslice_dict['backend'])
1603 uploader = ManifoldUploader (logger=self.logger,
1604 url=myslice_dict['backend'],
1605 platform=myslice_dict['platform'],
1606 username=myslice_dict['username'],
1607 password=options.password)
1608 uploader.prompt_all()
1609 (count_all,count_success)=(0,0)
1610 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1612 inspect=Credential(string=delegated_credential)
1613 expire_datetime=inspect.get_expiration()
1614 message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
1615 if uploader.upload(delegated_credential,message=message):
1618 self.logger.info("Successfully uploaded %d/%d credentials"%(count_success,count_all))
1620 # at first I thought we would want to save these,
1621 # like 'sfi delegate does' but on second thought
1622 # it is probably not helpful as people would not
1623 # need to run 'sfi delegate' at all anymore
1624 if count_success != count_all: sys.exit(1)
1627 @register_command("cred","")
1628 def trusted(self, options, args):
1630 return the trusted certs at this interface (get_trusted_certs)
1632 if options.version_registry:
1633 server=self.registry()
1635 server = self.sliceapi()
1637 auth_cred = self.my_authority_credential_string()
1638 trusted_certs = server.get_trusted_certs(auth_cred)
1640 if not options.version_registry:
1641 trusted_certs = ReturnValue.get_value(trusted_certs)
1643 for trusted_cert in trusted_certs:
1644 print "\n===========================================================\n"
1645 gid = GID(string=trusted_cert)
1647 cert = Certificate(string=trusted_cert)
1648 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1649 print "Certificate:\n%s\n\n"%trusted_cert