2 # sfi.py - basic SFA command-line client
3 # this module is also used in sfascan
17 from lxml import etree
18 from StringIO import StringIO
19 from optparse import OptionParser
20 from pprint import PrettyPrinter
21 from tempfile import mkstemp
23 from sfa.trust.certificate import Keypair, Certificate
24 from sfa.trust.gid import GID
25 from sfa.trust.credential import Credential
26 from sfa.trust.sfaticket import SfaTicket
28 from sfa.util.faults import SfaInvalidArgument
29 from sfa.util.sfalogging import sfi_logger
30 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
31 from sfa.util.config import Config
32 from sfa.util.version import version_core
33 from sfa.util.cache import Cache
35 from sfa.storage.record import Record
37 from sfa.rspecs.rspec import RSpec
38 from sfa.rspecs.rspec_converter import RSpecConverter
39 from sfa.rspecs.version_manager import VersionManager
41 from sfa.client.sfaclientlib import SfaClientBootstrap
42 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
43 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
44 from sfa.client.return_value import ReturnValue
45 from sfa.client.candidates import Candidates
46 from sfa.client.manifolduploader import ManifoldUploader
50 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
51 terminal_render, filter_records
54 def display_rspec(rspec, format='rspec'):
56 tree = etree.parse(StringIO(rspec))
58 result = root.xpath("./network/site/node/hostname/text()")
59 elif format in ['ip']:
60 # The IP address is not yet part of the new RSpec
61 # so this doesn't do anything yet.
62 tree = etree.parse(StringIO(rspec))
64 result = root.xpath("./network/site/node/ipv4/text()")
71 def display_list(results):
72 for result in results:
75 def display_records(recordList, dump=False):
76 ''' Print all fields in the record'''
77 for record in recordList:
78 display_record(record, dump)
80 def display_record(record, dump=False):
82 record.dump(sort=True)
84 info = record.getdict()
85 print "%s (%s)" % (info['hrn'], info['type'])
89 def filter_records(type, records):
91 for record in records:
92 if (record['type'] == type) or (type == "all"):
93 filtered_records.append(record)
94 return filtered_records
97 def credential_printable (cred):
98 credential=Credential(cred=cred)
100 result += credential.get_summary_tostring()
102 rights = credential.get_privileges()
103 result += "type=%s\n" % credential.type
104 result += "version=%s\n" % credential.version
105 result += "rights=%s\n"%rights
108 def show_credentials (cred_s):
109 if not isinstance (cred_s,list): cred_s = [cred_s]
111 print "Using Credential %s"%credential_printable(cred)
114 def save_raw_to_file(var, filename, format="text", banner=None):
116 # if filename is "-", send it to stdout
119 f = open(filename, "w")
124 elif format == "pickled":
125 f.write(pickle.dumps(var))
126 elif format == "json":
127 if hasattr(json, "dumps"):
128 f.write(json.dumps(var)) # python 2.6
130 f.write(json.write(var)) # python 2.5
132 # this should never happen
133 print "unknown output format", format
135 f.write('\n'+banner+"\n")
137 def save_rspec_to_file(rspec, filename):
138 if not filename.endswith(".rspec"):
139 filename = filename + ".rspec"
140 f = open(filename, 'w')
145 def save_records_to_file(filename, record_dicts, format="xml"):
148 for record_dict in record_dicts:
150 save_record_to_file(filename + "." + str(index), record_dict)
152 save_record_to_file(filename, record_dict)
154 elif format == "xmllist":
155 f = open(filename, "w")
156 f.write("<recordlist>\n")
157 for record_dict in record_dicts:
158 record_obj=Record(dict=record_dict)
159 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
160 f.write("</recordlist>\n")
162 elif format == "hrnlist":
163 f = open(filename, "w")
164 for record_dict in record_dicts:
165 record_obj=Record(dict=record_dict)
166 f.write(record_obj.hrn + "\n")
169 # this should never happen
170 print "unknown output format", format
172 def save_record_to_file(filename, record_dict):
173 record = Record(dict=record_dict)
174 xml = record.save_as_xml()
175 f=codecs.open(filename, encoding='utf-8',mode="w")
180 # minimally check a key argument
181 def check_ssh_key (key):
182 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
183 return re.match(good_ssh_key, key, re.IGNORECASE)
186 def load_record_from_opts(options):
188 if hasattr(options, 'xrn') and options.xrn:
189 if hasattr(options, 'type') and options.type:
190 xrn = Xrn(options.xrn, options.type)
192 xrn = Xrn(options.xrn)
193 record_dict['urn'] = xrn.get_urn()
194 record_dict['hrn'] = xrn.get_hrn()
195 record_dict['type'] = xrn.get_type()
196 if hasattr(options, 'key') and options.key:
198 pubkey = open(options.key, 'r').read()
201 if not check_ssh_key (pubkey):
202 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
203 record_dict['keys'] = [pubkey]
204 if hasattr(options, 'slices') and options.slices:
205 record_dict['slices'] = options.slices
206 if hasattr(options, 'researchers') and options.researchers:
207 record_dict['researcher'] = options.researchers
208 if hasattr(options, 'email') and options.email:
209 record_dict['email'] = options.email
210 if hasattr(options, 'pis') and options.pis:
211 record_dict['pi'] = options.pis
213 # handle extra settings
214 record_dict.update(options.extras)
216 return Record(dict=record_dict)
218 def load_record_from_file(filename):
219 f=codecs.open(filename, encoding="utf-8", mode="r")
220 xml_string = f.read()
222 return Record(xml=xml_string)
226 def unique_call_id(): return uuid.uuid4().urn
228 ########## a simple model for maintaing 3 doc attributes per command (instead of just one)
229 # essentially for the methods that implement a subcommand like sfi list
230 # we need to keep track of
231 # (*) doc a few lines that tell what it does, still located in __doc__
232 # (*) args_string a simple one-liner that describes mandatory arguments
233 # (*) example well, one or several releant examples
235 # since __doc__ only accounts for one, we use this simple mechanism below
236 # however we keep doc in place for easier migration
238 from functools import wraps
240 # we use a list as well as a dict so we can keep track of the order
244 def register_command (args_string, example,aliases=None):
246 name=getattr(m,'__name__')
247 doc=getattr(m,'__doc__',"-- missing doc --")
248 doc=doc.strip(" \t\n")
249 commands_list.append(name)
250 # last item is 'canonical' name, so we can know which commands are aliases
251 command_tuple=(doc, args_string, example,name)
252 commands_dict[name]=command_tuple
253 if aliases is not None:
254 for alias in aliases:
255 commands_list.append(alias)
256 commands_dict[alias]=command_tuple
258 def new_method (*args, **kwds): return m(*args, **kwds)
266 # dirty hack to make this class usable from the outside
267 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
270 def default_sfi_dir ():
271 if os.path.isfile("./sfi_config"):
274 return os.path.expanduser("~/.sfi/")
276 # dummy to meet Sfi's expectations for its 'options' field
277 # i.e. s/t we can do setattr on
281 def __init__ (self,options=None):
282 if options is None: options=Sfi.DummyOptions()
283 for opt in Sfi.required_options:
284 if not hasattr(options,opt): setattr(options,opt,None)
285 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
286 self.options = options
288 self.authority = None
289 self.logger = sfi_logger
290 self.logger.enable_console()
291 ### various auxiliary material that we keep at hand
293 # need to call this other than just 'config' as we have a command/method with that name
294 self.config_instance=None
295 self.config_file=None
296 self.client_bootstrap=None
298 ### suitable if no reasonable command has been provided
299 def print_commands_help (self, options):
300 verbose=getattr(options,'verbose')
301 format3="%10s %-30s %s"
305 print format3%("command","cmd_args","description")
309 self.create_parser_global().print_help()
310 # preserve order from the code
311 for command in commands_list:
313 (doc, args_string, example, canonical) = commands_dict[command]
315 print "Cannot find info on command %s - skipped"%command
319 if command==canonical:
320 doc=doc.replace("\n","\n"+format3offset*' ')
321 print format3%(command,args_string,doc)
323 self.create_parser_command(command).print_help()
325 print format3%(command,"<<alias for %s>>"%canonical,"")
327 ### now if a known command was found we can be more verbose on that one
328 def print_help (self):
329 print "==================== Generic sfi usage"
330 self.sfi_parser.print_help()
331 (doc,_,example,canonical)=commands_dict[self.command]
332 if canonical != self.command:
333 print "\n==================== NOTE: %s is an alias for genuine %s"%(self.command,canonical)
334 self.command=canonical
335 print "\n==================== Purpose of %s"%self.command
337 print "\n==================== Specific usage for %s"%self.command
338 self.command_parser.print_help()
340 print "\n==================== %s example(s)"%self.command
343 def create_parser_global(self):
344 # Generate command line parser
345 parser = OptionParser(add_help_option=False,
346 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
347 description="Commands: %s"%(" ".join(commands_list)))
348 parser.add_option("-r", "--registry", dest="registry",
349 help="root registry", metavar="URL", default=None)
350 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
351 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
352 parser.add_option("-R", "--raw", dest="raw", default=None,
353 help="Save raw, unparsed server response to a file")
354 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
355 help="raw file format ([text]|pickled|json)", default="text",
356 choices=("text","pickled","json"))
357 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
358 help="text string to write before and after raw output")
359 parser.add_option("-d", "--dir", dest="sfi_dir",
360 help="config & working directory - default is %default",
361 metavar="PATH", default=Sfi.default_sfi_dir())
362 parser.add_option("-u", "--user", dest="user",
363 help="user name", metavar="HRN", default=None)
364 parser.add_option("-a", "--auth", dest="auth",
365 help="authority name", metavar="HRN", default=None)
366 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
367 help="verbose mode - cumulative")
368 parser.add_option("-D", "--debug",
369 action="store_true", dest="debug", default=False,
370 help="Debug (xml-rpc) protocol messages")
371 # would it make sense to use ~/.ssh/id_rsa as a default here ?
372 parser.add_option("-k", "--private-key",
373 action="store", dest="user_private_key", default=None,
374 help="point to the private key file to use if not yet installed in sfi_dir")
375 parser.add_option("-t", "--timeout", dest="timeout", default=None,
376 help="Amout of time to wait before timing out the request")
377 parser.add_option("-h", "--help",
378 action="store_true", dest="help", default=False,
379 help="one page summary on commands & exit")
380 parser.disable_interspersed_args()
385 def create_parser_command(self, command):
386 if command not in commands_dict:
387 msg="Invalid command\n"
389 msg += ','.join(commands_list)
390 self.logger.critical(msg)
393 # retrieve args_string
394 (_, args_string, __,___) = commands_dict[command]
396 parser = OptionParser(add_help_option=False,
397 usage="sfi [sfi_options] %s [cmd_options] %s"
398 % (command, args_string))
399 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
400 help="Summary of one command usage")
402 if command in ("config"):
403 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
404 help='how myslice config variables as well')
406 if command in ("version"):
407 parser.add_option("-l","--local",
408 action="store_true", dest="version_local", default=False,
409 help="display version of the local client")
411 if command in ("version", "trusted"):
412 parser.add_option("-R","--registry_interface",
413 action="store_true", dest="registry_interface", default=False,
414 help="target the registry interface instead of slice interface")
416 if command in ("register", "update"):
417 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
418 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
419 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
420 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
422 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
423 default='', type="str", action='callback', callback=optparse_listvalue_callback)
424 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
425 help='Set/replace slice researchers', default='', type="str", action='callback',
426 callback=optparse_listvalue_callback)
427 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
428 default='', type="str", action='callback', callback=optparse_listvalue_callback)
429 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
430 action="callback", callback=optparse_dictvalue_callback, nargs=1,
431 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
433 # user specifies remote aggregate/sm/component
434 if command in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
435 "action", "shutdown", "renew", "status"):
436 parser.add_option("-d", "--delegate", dest="delegate", default=None,
438 help="Include a credential delegated to the user's root"+\
439 "authority in set of credentials for this call")
441 # show_credential option
442 if command in ("list","resources", "describe", "provision", "allocate", "register","update","remove","delete","status","renew"):
443 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
444 help="show credential(s) used in human-readable form")
445 # registy filter option
446 if command in ("list", "show", "remove"):
447 parser.add_option("-t", "--type", dest="type", type="choice",
448 help="type filter ([all]|user|slice|authority|node|aggregate)",
449 choices=("all", "user", "slice", "authority", "node", "aggregate"),
451 if command in ("show"):
452 parser.add_option("-k","--key",dest="keys",action="append",default=[],
453 help="specify specific keys to be displayed from record")
454 if command in ("resources", "describe"):
456 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="GENI 3",
457 help="schema type and version of resulting RSpec")
458 # disable/enable cached rspecs
459 parser.add_option("-c", "--current", dest="current", default=False,
461 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
463 parser.add_option("-f", "--format", dest="format", type="choice",
464 help="display format ([xml]|dns|ip)", default="xml",
465 choices=("xml", "dns", "ip"))
466 #panos: a new option to define the type of information about resources a user is interested in
467 parser.add_option("-i", "--info", dest="info",
468 help="optional component information", default=None)
469 # a new option to retreive or not reservation-oriented RSpecs (leases)
470 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
471 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
472 choices=("all", "resources", "leases"), default="resources")
475 if command in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
476 parser.add_option("-o", "--output", dest="file",
477 help="output XML to file", metavar="FILE", default=None)
479 if command in ("show", "list"):
480 parser.add_option("-f", "--format", dest="format", type="choice",
481 help="display format ([text]|xml)", default="text",
482 choices=("text", "xml"))
484 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
485 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
486 choices=("xml", "xmllist", "hrnlist"))
487 if command == 'list':
488 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
489 help="list all child records", default=False)
490 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
491 help="gives details, like user keys", default=False)
492 if command in ("delegate"):
493 parser.add_option("-u", "--user",
494 action="store_true", dest="delegate_user", default=False,
495 help="delegate your own credentials; default if no other option is provided")
496 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
497 metavar="slice_hrn", help="delegate cred. for slice HRN")
498 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
499 metavar='auth_hrn', help="delegate cred for auth HRN")
500 # this primarily is a shorthand for -a my_hrn
501 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
502 help="delegate your PI credentials, so s.t. like -a your_hrn^")
503 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
504 help="""by default the mandatory argument is expected to be a user,
505 use this if you mean an authority instead""")
507 if command in ("myslice"):
508 parser.add_option("-p","--password",dest='password',action='store',default=None,
509 help="specify mainfold password on the command line")
510 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
511 metavar="slice_hrn", help="delegate cred. for slice HRN")
512 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
513 metavar='auth_hrn', help="delegate PI cred for auth HRN")
514 parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
515 parser.add_option('-b', '--backend', dest='backend', help="Override 'backend' from the config file")
521 # Main: parse arguments and dispatch to command
523 def dispatch(self, command, command_options, command_args):
524 method=getattr(self, command, None)
526 print "Unknown command %s"%command
528 return method(command_options, command_args)
531 self.sfi_parser = self.create_parser_global()
532 (options, args) = self.sfi_parser.parse_args()
534 self.print_commands_help(options)
536 self.options = options
538 self.logger.setLevelFromOptVerbose(self.options.verbose)
541 self.logger.critical("No command given. Use -h for help.")
542 self.print_commands_help(options)
545 # complete / find unique match with command set
546 command_candidates = Candidates (commands_list)
548 command = command_candidates.only_match(input)
550 self.print_commands_help(options)
552 # second pass options parsing
554 self.command_parser = self.create_parser_command(command)
555 (command_options, command_args) = self.command_parser.parse_args(args[1:])
556 if command_options.help:
559 self.command_options = command_options
563 self.logger.debug("Command=%s" % self.command)
566 self.dispatch(command, command_options, command_args)
570 self.logger.log_exc ("sfi command %s failed"%command)
576 def read_config(self):
577 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
578 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
580 if Config.is_ini(config_file):
581 config = Config (config_file)
583 # try upgrading from shell config format
584 fp, fn = mkstemp(suffix='sfi_config', text=True)
586 # we need to preload the sections we want parsed
587 # from the shell config
588 config.add_section('sfi')
589 # sface users should be able to use this same file to configure their stuff
590 config.add_section('sface')
591 # manifold users should be able to specify the details
592 # of their backend server here for 'sfi myslice'
593 config.add_section('myslice')
594 config.load(config_file)
596 shutil.move(config_file, shell_config_file)
598 config.save(config_file)
601 self.logger.critical("Failed to read configuration file %s"%config_file)
602 self.logger.info("Make sure to remove the export clauses and to add quotes")
603 if self.options.verbose==0:
604 self.logger.info("Re-run with -v for more details")
606 self.logger.log_exc("Could not read config file %s"%config_file)
609 self.config_instance=config
612 if (self.options.sm is not None):
613 self.sm_url = self.options.sm
614 elif hasattr(config, "SFI_SM"):
615 self.sm_url = config.SFI_SM
617 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
621 if (self.options.registry is not None):
622 self.reg_url = self.options.registry
623 elif hasattr(config, "SFI_REGISTRY"):
624 self.reg_url = config.SFI_REGISTRY
626 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
630 if (self.options.user is not None):
631 self.user = self.options.user
632 elif hasattr(config, "SFI_USER"):
633 self.user = config.SFI_USER
635 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
639 if (self.options.auth is not None):
640 self.authority = self.options.auth
641 elif hasattr(config, "SFI_AUTH"):
642 self.authority = config.SFI_AUTH
644 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
647 self.config_file=config_file
652 # Get various credential and spec files
654 # Establishes limiting conventions
655 # - conflates MAs and SAs
656 # - assumes last token in slice name is unique
658 # Bootstraps credentials
659 # - bootstrap user credential from self-signed certificate
660 # - bootstrap authority credential from user credential
661 # - bootstrap slice credential from user credential
664 # init self-signed cert, user credentials and gid
665 def bootstrap (self):
666 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
668 # if -k is provided, use this to initialize private key
669 if self.options.user_private_key:
670 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
672 # trigger legacy compat code if needed
673 # the name has changed from just <leaf>.pkey to <hrn>.pkey
674 if not os.path.isfile(client_bootstrap.private_key_filename()):
675 self.logger.info ("private key not found, trying legacy name")
677 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
678 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
679 client_bootstrap.init_private_key_if_missing (legacy_private_key)
680 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
682 self.logger.log_exc("Can't find private key ")
686 client_bootstrap.bootstrap_my_gid()
687 # extract what's needed
688 self.private_key = client_bootstrap.private_key()
689 self.my_credential_string = client_bootstrap.my_credential_string ()
690 self.my_credential = {'geni_type': 'geni_sfa',
692 'geni_value': self.my_credential_string}
693 self.my_gid = client_bootstrap.my_gid ()
694 self.client_bootstrap = client_bootstrap
697 def my_authority_credential_string(self):
698 if not self.authority:
699 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
701 return self.client_bootstrap.authority_credential_string (self.authority)
703 def authority_credential_string(self, auth_hrn):
704 return self.client_bootstrap.authority_credential_string (auth_hrn)
706 def slice_credential_string(self, name):
707 return self.client_bootstrap.slice_credential_string (name)
709 def slice_credential(self, name):
710 return {'geni_type': 'geni_sfa',
712 'geni_value': self.slice_credential_string(name)}
714 # xxx should be supported by sfaclientbootstrap as well
715 def delegate_cred(self, object_cred, hrn, type='authority'):
716 # the gid and hrn of the object we are delegating
717 if isinstance(object_cred, str):
718 object_cred = Credential(string=object_cred)
719 object_gid = object_cred.get_gid_object()
720 object_hrn = object_gid.get_hrn()
722 if not object_cred.get_privileges().get_all_delegate():
723 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
726 # the delegating user's gid
727 caller_gidfile = self.my_gid()
729 # the gid of the user who will be delegated to
730 delegee_gid = self.client_bootstrap.gid(hrn,type)
731 delegee_hrn = delegee_gid.get_hrn()
732 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
733 return dcred.save_to_string(save_parents=True)
736 # Management of the servers
741 if not hasattr (self, 'registry_proxy'):
742 self.logger.info("Contacting Registry at: %s"%self.reg_url)
743 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
744 timeout=self.options.timeout, verbose=self.options.debug)
745 return self.registry_proxy
749 if not hasattr (self, 'sliceapi_proxy'):
750 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
751 if hasattr(self.command_options,'component') and self.command_options.component:
752 # resolve the hrn at the registry
753 node_hrn = self.command_options.component
754 records = self.registry().Resolve(node_hrn, self.my_credential_string)
755 records = filter_records('node', records)
757 self.logger.warning("No such component:%r"% opts.component)
759 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
760 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
762 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
763 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
764 self.sm_url = 'http://' + self.sm_url
765 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
766 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
767 timeout=self.options.timeout, verbose=self.options.debug)
768 return self.sliceapi_proxy
770 def get_cached_server_version(self, server):
771 # check local cache first
774 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
775 cache_key = server.url + "-version"
777 cache = Cache(cache_file)
780 self.logger.info("Local cache not found at: %s" % cache_file)
783 version = cache.get(cache_key)
786 result = server.GetVersion()
787 version= ReturnValue.get_value(result)
788 # cache version for 20 minutes
789 cache.add(cache_key, version, ttl= 60*20)
790 self.logger.info("Updating cache file %s" % cache_file)
791 cache.save_to_file(cache_file)
795 ### resurrect this temporarily so we can support V1 aggregates for a while
796 def server_supports_options_arg(self, server):
798 Returns true if server support the optional call_id arg, false otherwise.
800 server_version = self.get_cached_server_version(server)
802 # xxx need to rewrite this
803 if int(server_version.get('geni_api')) >= 2:
807 def server_supports_call_id_arg(self, server):
808 server_version = self.get_cached_server_version(server)
810 if 'sfa' in server_version and 'code_tag' in server_version:
811 code_tag = server_version['code_tag']
812 code_tag_parts = code_tag.split("-")
813 version_parts = code_tag_parts[0].split(".")
814 major, minor = version_parts[0], version_parts[1]
815 rev = code_tag_parts[1]
816 if int(major) == 1 and minor == 0 and build >= 22:
820 ### ois = options if supported
821 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
822 def ois (self, server, option_dict):
823 if self.server_supports_options_arg (server):
825 elif self.server_supports_call_id_arg (server):
826 return [ unique_call_id () ]
830 ### cis = call_id if supported - like ois
831 def cis (self, server):
832 if self.server_supports_call_id_arg (server):
833 return [ unique_call_id ]
837 ######################################## miscell utilities
838 def get_rspec_file(self, rspec):
839 if (os.path.isabs(rspec)):
842 file = os.path.join(self.options.sfi_dir, rspec)
843 if (os.path.isfile(file)):
846 self.logger.critical("No such rspec file %s"%rspec)
849 def get_record_file(self, record):
850 if (os.path.isabs(record)):
853 file = os.path.join(self.options.sfi_dir, record)
854 if (os.path.isfile(file)):
857 self.logger.critical("No such registry record file %s"%record)
861 #==========================================================================
862 # Following functions implement the commands
864 # Registry-related commands
865 #==========================================================================
867 @register_command("","")
868 def config (self, options, args):
869 "Display contents of current config"
870 print "# From configuration file %s"%self.config_file
871 flags=[ ('sfi', [ ('registry','reg_url'),
872 ('auth','authority'),
878 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
880 for (section, tuples) in flags:
883 for (external_name, internal_name) in tuples:
884 print "%-20s = %s"%(external_name,getattr(self,internal_name))
887 varname="%s_%s"%(section.upper(),name.upper())
888 value=getattr(self.config_instance,varname)
889 print "%-20s = %s"%(name,value)
891 @register_command("","")
892 def version(self, options, args):
894 display an SFA server version (GetVersion)
895 or version information about sfi itself
897 if options.version_local:
898 version=version_core()
900 if options.registry_interface:
901 server=self.registry()
903 server = self.sliceapi()
904 result = server.GetVersion()
905 version = ReturnValue.get_value(result)
907 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
909 pprinter = PrettyPrinter(indent=4)
910 pprinter.pprint(version)
912 @register_command("authority","")
913 def list(self, options, args):
915 list entries in named authority registry (List)
922 if options.recursive:
923 opts['recursive'] = options.recursive
925 if options.show_credential:
926 show_credentials(self.my_credential_string)
928 list = self.registry().List(hrn, self.my_credential_string, options)
930 raise Exception, "Not enough parameters for the 'list' command"
932 # filter on person, slice, site, node, etc.
933 # This really should be in the self.filter_records funct def comment...
934 list = filter_records(options.type, list)
935 terminal_render (list, options)
937 save_records_to_file(options.file, list, options.fileformat)
940 @register_command("name","")
941 def show(self, options, args):
943 show details about named registry record (Resolve)
949 # explicitly require Resolve to run in details mode
950 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
951 record_dicts = filter_records(options.type, record_dicts)
953 self.logger.error("No record of type %s"% options.type)
955 # user has required to focus on some keys
957 def project (record):
959 for key in options.keys:
960 try: projected[key]=record[key]
963 record_dicts = [ project (record) for record in record_dicts ]
964 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
965 for record in records:
966 if (options.format == "text"): record.dump(sort=True)
967 else: print record.save_as_xml()
969 save_records_to_file(options.file, record_dicts, options.fileformat)
972 # this historically was named 'add', it is now 'register' with an alias for legacy
973 @register_command("[xml-filename]","",['add'])
974 def register(self, options, args):
975 """create new record in registry (Register)
976 from command line options (recommended)
977 old-school method involving an xml file still supported"""
979 auth_cred = self.my_authority_credential_string()
980 if options.show_credential:
981 show_credentials(auth_cred)
988 record_filepath = args[0]
989 rec_file = self.get_record_file(record_filepath)
990 record_dict.update(load_record_from_file(rec_file).todict())
992 print "Cannot load record file %s"%record_filepath
995 record_dict.update(load_record_from_opts(options).todict())
996 # we should have a type by now
997 if 'type' not in record_dict :
1000 # this is still planetlab dependent.. as plc will whine without that
1001 # also, it's only for adding
1002 if record_dict['type'] == 'user':
1003 if not 'first_name' in record_dict:
1004 record_dict['first_name'] = record_dict['hrn']
1005 if 'last_name' not in record_dict:
1006 record_dict['last_name'] = record_dict['hrn']
1007 return self.registry().Register(record_dict, auth_cred)
1009 @register_command("[xml-filename]","")
1010 def update(self, options, args):
1011 """update record into registry (Update)
1012 from command line options (recommended)
1013 old-school method involving an xml file still supported"""
1016 record_filepath = args[0]
1017 rec_file = self.get_record_file(record_filepath)
1018 record_dict.update(load_record_from_file(rec_file).todict())
1020 record_dict.update(load_record_from_opts(options).todict())
1021 # at the very least we need 'type' here
1022 if 'type' not in record_dict:
1026 # don't translate into an object, as this would possibly distort
1027 # user-provided data; e.g. add an 'email' field to Users
1028 if record_dict['type'] == "user":
1029 if record_dict['hrn'] == self.user:
1030 cred = self.my_credential_string
1032 cred = self.my_authority_credential_string()
1033 elif record_dict['type'] in ["slice"]:
1035 cred = self.slice_credential_string(record_dict['hrn'])
1036 except ServerException, e:
1037 # XXX smbaker -- once we have better error return codes, update this
1038 # to do something better than a string compare
1039 if "Permission error" in e.args[0]:
1040 cred = self.my_authority_credential_string()
1043 elif record_dict['type'] in ["authority"]:
1044 cred = self.my_authority_credential_string()
1045 elif record_dict['type'] == 'node':
1046 cred = self.my_authority_credential_string()
1048 raise "unknown record type" + record_dict['type']
1049 if options.show_credential:
1050 show_credentials(cred)
1051 return self.registry().Update(record_dict, cred)
1053 @register_command("hrn","")
1054 def remove(self, options, args):
1055 "remove registry record by name (Remove)"
1056 auth_cred = self.my_authority_credential_string()
1064 if options.show_credential:
1065 show_credentials(auth_cred)
1066 return self.registry().Remove(hrn, auth_cred, type)
1068 # ==================================================================
1069 # Slice-related commands
1070 # ==================================================================
1072 # show rspec for named slice
1073 @register_command("","")
1074 def resources(self, options, args):
1076 discover available resources (ListResources)
1078 server = self.sliceapi()
1081 creds = [self.my_credential]
1082 if options.delegate:
1083 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1084 if options.show_credential:
1085 show_credentials(creds)
1087 # no need to check if server accepts the options argument since the options has
1088 # been a required argument since v1 API
1090 # always send call_id to v2 servers
1091 api_options ['call_id'] = unique_call_id()
1092 # ask for cached value if available
1093 api_options ['cached'] = True
1095 api_options['info'] = options.info
1096 if options.list_leases:
1097 api_options['list_leases'] = options.list_leases
1099 if options.current == True:
1100 api_options['cached'] = False
1102 api_options['cached'] = True
1103 if options.rspec_version:
1104 version_manager = VersionManager()
1105 server_version = self.get_cached_server_version(server)
1106 if 'sfa' in server_version:
1107 # just request the version the client wants
1108 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1110 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1112 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1113 result = server.ListResources (creds, api_options)
1114 value = ReturnValue.get_value(result)
1115 if self.options.raw:
1116 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1117 if options.file is not None:
1118 save_rspec_to_file(value, options.file)
1119 if (self.options.raw is None) and (options.file is None):
1120 display_rspec(value, options.format)
1124 @register_command("slice_hrn","")
1125 def describe(self, options, args):
1127 shows currently allocated/provisioned resources
1128 of the named slice or set of slivers (Describe)
1130 server = self.sliceapi()
1133 creds = [self.slice_credential(args[0])]
1134 if options.delegate:
1135 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1136 if options.show_credential:
1137 show_credentials(creds)
1139 api_options = {'call_id': unique_call_id(),
1141 #'info': options.info,
1142 'list_leases': options.list_leases,
1143 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1146 api_options['info'] = options.info
1148 if options.rspec_version:
1149 version_manager = VersionManager()
1150 server_version = self.get_cached_server_version(server)
1151 if 'sfa' in server_version:
1152 # just request the version the client wants
1153 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1155 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1156 urn = Xrn(args[0], type='slice').get_urn()
1157 result = server.Describe([urn], creds, api_options)
1158 value = ReturnValue.get_value(result)
1159 if self.options.raw:
1160 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1161 if options.file is not None:
1162 save_rspec_to_file(value['geni_rspec'], options.file)
1163 if (self.options.raw is None) and (options.file is None):
1164 display_rspec(value, options.format)
1168 @register_command("slice_hrn [<sliver_urn>...]","")
1169 def delete(self, options, args):
1171 de-allocate and de-provision all or named slivers of the named slice (Delete)
1173 server = self.sliceapi()
1177 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1180 # we have sliver urns
1181 sliver_urns = args[1:]
1183 # we provision all the slivers of the slice
1184 sliver_urns = [slice_urn]
1187 slice_cred = self.slice_credential(slice_hrn)
1188 creds = [slice_cred]
1190 # options and call_id when supported
1192 api_options ['call_id'] = unique_call_id()
1193 if options.show_credential:
1194 show_credentials(creds)
1195 result = server.Delete(sliver_urns, creds, *self.ois(server, api_options ) )
1196 value = ReturnValue.get_value(result)
1197 if self.options.raw:
1198 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1203 @register_command("slice_hrn rspec","")
1204 def allocate(self, options, args):
1206 allocate resources to the named slice (Allocate)
1208 server = self.sliceapi()
1209 server_version = self.get_cached_server_version(server)
1211 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1214 creds = [self.slice_credential(slice_hrn)]
1216 delegated_cred = None
1217 if server_version.get('interface') == 'slicemgr':
1218 # delegate our cred to the slice manager
1219 # do not delegate cred to slicemgr...not working at the moment
1221 #if server_version.get('hrn'):
1222 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1223 #elif server_version.get('urn'):
1224 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1226 if options.show_credential:
1227 show_credentials(creds)
1230 rspec_file = self.get_rspec_file(args[1])
1231 rspec = open(rspec_file).read()
1233 api_options ['call_id'] = unique_call_id()
1237 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1238 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1239 slice_record = slice_records[0]
1240 user_hrns = slice_record['reg-researchers']
1241 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1242 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1243 sfa_users = sfa_users_arg(user_records, slice_record)
1244 geni_users = pg_users_arg(user_records)
1246 api_options['sfa_users'] = sfa_users
1247 api_options['geni_users'] = geni_users
1249 result = server.Allocate(slice_urn, creds, rspec, api_options)
1250 value = ReturnValue.get_value(result)
1251 if self.options.raw:
1252 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1253 if options.file is not None:
1254 save_rspec_to_file (value['geni_rspec'], options.file)
1255 if (self.options.raw is None) and (options.file is None):
1260 @register_command("slice_hrn [<sliver_urn>...]","")
1261 def provision(self, options, args):
1263 provision all or named already allocated slivers of the named slice (Provision)
1265 server = self.sliceapi()
1266 server_version = self.get_cached_server_version(server)
1268 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1270 # we have sliver urns
1271 sliver_urns = args[1:]
1273 # we provision all the slivers of the slice
1274 sliver_urns = [slice_urn]
1277 creds = [self.slice_credential(slice_hrn)]
1278 delegated_cred = None
1279 if server_version.get('interface') == 'slicemgr':
1280 # delegate our cred to the slice manager
1281 # do not delegate cred to slicemgr...not working at the moment
1283 #if server_version.get('hrn'):
1284 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1285 #elif server_version.get('urn'):
1286 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1288 if options.show_credential:
1289 show_credentials(creds)
1292 api_options ['call_id'] = unique_call_id()
1294 # set the requtested rspec version
1295 version_manager = VersionManager()
1296 rspec_version = version_manager._get_version('geni', '3').to_dict()
1297 api_options['geni_rspec_version'] = rspec_version
1300 # need to pass along user keys to the aggregate.
1302 # { urn: urn:publicid:IDN+emulab.net+user+alice
1303 # keys: [<ssh key A>, <ssh key B>]
1306 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1307 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1308 slice_record = slice_records[0]
1309 user_hrns = slice_record['reg-researchers']
1310 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1311 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1312 users = pg_users_arg(user_records)
1314 api_options['geni_users'] = users
1315 result = server.Provision(sliver_urns, creds, api_options)
1316 value = ReturnValue.get_value(result)
1317 if self.options.raw:
1318 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1319 if options.file is not None:
1320 save_rspec_to_file (value['geni_rspec'], options.file)
1321 if (self.options.raw is None) and (options.file is None):
1325 @register_command("slice_hrn","")
1326 def status(self, options, args):
1328 retrieve the status of the slivers belonging to the named slice (Status)
1330 server = self.sliceapi()
1334 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1337 slice_cred = self.slice_credential(slice_hrn)
1338 creds = [slice_cred]
1340 # options and call_id when supported
1342 api_options['call_id']=unique_call_id()
1343 if options.show_credential:
1344 show_credentials(creds)
1345 result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1346 value = ReturnValue.get_value(result)
1347 if self.options.raw:
1348 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1351 # Thierry: seemed to be missing
1354 @register_command("slice_hrn [<sliver_urn>...] action","")
1355 def action(self, options, args):
1357 Perform the named operational action on all or named slivers of the named slice
1359 server = self.sliceapi()
1363 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1365 # we have sliver urns
1366 sliver_urns = args[1:-1]
1368 # we provision all the slivers of the slice
1369 sliver_urns = [slice_urn]
1372 slice_cred = self.slice_credential(args[0])
1373 creds = [slice_cred]
1374 if options.delegate:
1375 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1376 creds.append(delegated_cred)
1378 result = server.PerformOperationalAction(sliver_urns, creds, action , api_options)
1379 value = ReturnValue.get_value(result)
1380 if self.options.raw:
1381 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1386 @register_command("slice_hrn [<sliver_urn>...] time","")
1387 def renew(self, options, args):
1391 server = self.sliceapi()
1396 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1399 # we have sliver urns
1400 sliver_urns = args[1:-1]
1402 # we provision all the slivers of the slice
1403 sliver_urns = [slice_urn]
1404 input_time = args[-1]
1406 # time: don't try to be smart on the time format, server-side will
1408 slice_cred = self.slice_credential(args[0])
1409 creds = [slice_cred]
1410 # options and call_id when supported
1412 api_options['call_id']=unique_call_id()
1413 if options.show_credential:
1414 show_credentials(creds)
1415 result = server.Renew(sliver_urns, creds, input_time, *self.ois(server,api_options))
1416 value = ReturnValue.get_value(result)
1417 if self.options.raw:
1418 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1424 @register_command("slice_hrn","")
1425 def shutdown(self, options, args):
1427 shutdown named slice (Shutdown)
1429 server = self.sliceapi()
1432 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1434 slice_cred = self.slice_credential(slice_hrn)
1435 creds = [slice_cred]
1436 result = server.Shutdown(slice_urn, creds)
1437 value = ReturnValue.get_value(result)
1438 if self.options.raw:
1439 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1445 @register_command("[name]","")
1446 def gid(self, options, args):
1448 Create a GID (CreateGid)
1453 target_hrn = args[0]
1454 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1455 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1457 filename = options.file
1459 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1460 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1461 GID(string=gid).save_to_file(filename)
1463 ####################
1464 @register_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1466 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1467 the set of credentials in the scope for this call would be
1468 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1470 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1472 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1473 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1474 because of the two -s options
1477 def delegate (self, options, args):
1479 (locally) create delegate credential for use by given hrn
1480 make sure to check for 'sfi myslice' instead if you plan
1487 # support for several delegations in the same call
1488 # so first we gather the things to do
1490 for slice_hrn in options.delegate_slices:
1491 message="%s.slice"%slice_hrn
1492 original = self.slice_credential_string(slice_hrn)
1493 tuples.append ( (message, original,) )
1494 if options.delegate_pi:
1495 my_authority=self.authority
1496 message="%s.pi"%my_authority
1497 original = self.my_authority_credential_string()
1498 tuples.append ( (message, original,) )
1499 for auth_hrn in options.delegate_auths:
1500 message="%s.auth"%auth_hrn
1501 original=self.authority_credential_string(auth_hrn)
1502 tuples.append ( (message, original, ) )
1503 # if nothing was specified at all at this point, let's assume -u
1504 if not tuples: options.delegate_user=True
1506 if options.delegate_user:
1507 message="%s.user"%self.user
1508 original = self.my_credential_string
1509 tuples.append ( (message, original, ) )
1511 # default type for beneficial is user unless -A
1512 if options.delegate_to_authority: to_type='authority'
1513 else: to_type='user'
1515 # let's now handle all this
1516 # it's all in the filenaming scheme
1517 for (message,original) in tuples:
1518 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1519 delegated_credential = Credential (string=delegated_string)
1520 filename = os.path.join ( self.options.sfi_dir,
1521 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1522 delegated_credential.save_to_file(filename, save_parents=True)
1523 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1525 ####################
1526 @register_command("","""$ less +/myslice sfi_config
1528 backend = http://manifold.pl.sophia.inria.fr:7080
1529 # the HRN that myslice uses, so that we are delegating to
1530 delegate = ple.upmc.slicebrowser
1531 # platform - this is a myslice concept
1533 # username - as of this writing (May 2013) a simple login name
1537 will first collect the slices that you are part of, then make sure
1538 all your credentials are up-to-date (read: refresh expired ones)
1539 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1540 and upload them all on myslice backend, using 'platform' and 'user'.
1541 A password will be prompted for the upload part.
1543 $ sfi -v myslice -- or sfi -vv myslice
1544 same but with more and more verbosity
1546 $ sfi m -b http://mymanifold.foo.com:7080/
1547 is synonym to sfi myslice as no other command starts with an 'm'
1548 and uses a custom backend for this one call
1550 ) # register_command
1551 def myslice (self, options, args):
1553 """ This helper is for refreshing your credentials at myslice; it will
1554 * compute all the slices that you currently have credentials on
1555 * refresh all your credentials (you as a user and pi, your slices)
1556 * upload them to the manifold backend server
1557 for last phase, sfi_config is read to look for the [myslice] section,
1558 and namely the 'backend', 'delegate' and 'user' settings"""
1564 # enable info by default
1565 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1566 ### the rough sketch goes like this
1567 # (0) produce a p12 file
1568 self.client_bootstrap.my_pkcs12()
1570 # (a) rain check for sufficient config in sfi_config
1572 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1573 for key in myslice_keys:
1575 # oct 2013 - I'm finding myself juggling with config files
1576 # so a couple of command-line options can now override config
1577 if hasattr(options,key) and getattr(options,key) is not None:
1578 value=getattr(options,key)
1580 full_key="MYSLICE_" + key.upper()
1581 value=getattr(self.config_instance,full_key,None)
1582 if value: myslice_dict[key]=value
1583 else: print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1584 if len(myslice_dict) != len(myslice_keys):
1587 # (b) figure whether we are PI for the authority where we belong
1588 self.logger.info("Resolving our own id %s"%self.user)
1589 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1590 if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1591 my_record=my_records[0]
1592 my_auths_all = my_record['reg-pi-authorities']
1593 self.logger.info("Found %d authorities that we are PI for"%len(my_auths_all))
1594 self.logger.debug("They are %s"%(my_auths_all))
1596 my_auths = my_auths_all
1597 if options.delegate_auths:
1598 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1599 self.logger.debug("Restricted to user-provided auths"%(my_auths))
1601 # (c) get the set of slices that we are in
1602 my_slices_all=my_record['reg-slices']
1603 self.logger.info("Found %d slices that we are member of"%len(my_slices_all))
1604 self.logger.debug("They are: %s"%(my_slices_all))
1606 my_slices = my_slices_all
1607 # if user provided slices, deal only with these - if they are found
1608 if options.delegate_slices:
1609 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1610 self.logger.debug("Restricted to user-provided slices: %s"%(my_slices))
1612 # (d) make sure we have *valid* credentials for all these
1614 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1615 for auth_hrn in my_auths:
1616 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1617 for slice_hrn in my_slices:
1618 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1620 # (e) check for the delegated version of these
1621 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1622 # switch to myslice using an authority instead of a user
1623 delegatee_type='user'
1624 delegatee_hrn=myslice_dict['delegate']
1625 hrn_delegated_credentials = []
1626 for (hrn, htype, credential) in hrn_credentials:
1627 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1628 # save these so user can monitor what she's uploaded
1629 filename = os.path.join ( self.options.sfi_dir,
1630 "%s.%s_for_%s.%s.cred"%(hrn,htype,delegatee_hrn,delegatee_type))
1631 with file(filename,'w') as f:
1632 f.write(delegated_credential)
1633 self.logger.debug("(Over)wrote %s"%filename)
1634 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1636 # (f) and finally upload them to manifold server
1637 # xxx todo add an option so the password can be set on the command line
1638 # (but *NOT* in the config file) so other apps can leverage this
1639 self.logger.info("Uploading on backend at %s"%myslice_dict['backend'])
1640 uploader = ManifoldUploader (logger=self.logger,
1641 url=myslice_dict['backend'],
1642 platform=myslice_dict['platform'],
1643 username=myslice_dict['username'],
1644 password=options.password)
1645 uploader.prompt_all()
1646 (count_all,count_success)=(0,0)
1647 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1649 inspect=Credential(string=delegated_credential)
1650 expire_datetime=inspect.get_expiration()
1651 message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
1652 if uploader.upload(delegated_credential,message=message):
1655 self.logger.info("Successfully uploaded %d/%d credentials"%(count_success,count_all))
1657 # at first I thought we would want to save these,
1658 # like 'sfi delegate does' but on second thought
1659 # it is probably not helpful as people would not
1660 # need to run 'sfi delegate' at all anymore
1661 if count_success != count_all: sys.exit(1)
1664 @register_command("cred","")
1665 def trusted(self, options, args):
1667 return the trusted certs at this interface (get_trusted_certs)
1669 if options.registry_interface:
1670 server=self.registry()
1672 server = self.sliceapi()
1673 cred = self.my_authority_credential_string()
1674 trusted_certs = server.get_trusted_certs(cred)
1675 if not options.registry_interface:
1676 trusted_certs = ReturnValue.get_value(trusted_certs)
1678 for trusted_cert in trusted_certs:
1679 print "\n===========================================================\n"
1680 gid = GID(string=trusted_cert)
1682 cert = Certificate(string=trusted_cert)
1683 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1684 print "Certificate:\n%s\n\n"%trusted_cert