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
34 from sfa.util.printable import printable
36 from sfa.storage.record import Record
38 from sfa.rspecs.rspec import RSpec
39 from sfa.rspecs.rspec_converter import RSpecConverter
40 from sfa.rspecs.version_manager import VersionManager
42 from sfa.client.sfaclientlib import SfaClientBootstrap
43 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
44 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
45 from sfa.client.return_value import ReturnValue
46 from sfa.client.candidates import Candidates
47 from sfa.client.manifolduploader import ManifoldUploader
50 DEFAULT_RSPEC_VERSION = "GENI 3"
52 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
53 terminal_render, filter_records
56 def display_rspec(rspec, format='rspec'):
58 tree = etree.parse(StringIO(rspec))
60 result = root.xpath("./network/site/node/hostname/text()")
61 elif format in ['ip']:
62 # The IP address is not yet part of the new RSpec
63 # so this doesn't do anything yet.
64 tree = etree.parse(StringIO(rspec))
66 result = root.xpath("./network/site/node/ipv4/text()")
73 def display_list(results):
74 for result in results:
77 def display_records(recordList, dump=False):
78 ''' Print all fields in the record'''
79 for record in recordList:
80 display_record(record, dump)
82 def display_record(record, dump=False):
84 record.dump(sort=True)
86 info = record.getdict()
87 print "{} ({})".format(info['hrn'], info['type'])
91 def filter_records(type, records):
93 for record in records:
94 if (record['type'] == type) or (type == "all"):
95 filtered_records.append(record)
96 return filtered_records
99 def credential_printable (cred):
100 credential = Credential(cred=cred)
102 result += credential.pretty_cred()
104 rights = credential.get_privileges()
105 result += "type={}\n".format(credential.type)
106 result += "version={}\n".format(credential.version)
107 result += "rights={}\n".format(rights)
110 def show_credentials (cred_s):
111 if not isinstance (cred_s,list): cred_s = [cred_s]
113 print "Using Credential {}".format(credential_printable(cred))
116 def save_raw_to_file(var, filename, format="text", banner=None):
118 # if filename is "-", send it to stdout
121 f = open(filename, "w")
126 elif format == "pickled":
127 f.write(pickle.dumps(var))
128 elif format == "json":
129 if hasattr(json, "dumps"):
130 f.write(json.dumps(var)) # python 2.6
132 f.write(json.write(var)) # python 2.5
134 # this should never happen
135 print "unknown output format", format
137 f.write('\n'+banner+"\n")
139 def save_rspec_to_file(rspec, filename):
140 if not filename.endswith(".rspec"):
141 filename = filename + ".rspec"
142 with open(filename, 'w') as f:
143 f.write("{}".format(rspec))
146 def save_records_to_file(filename, record_dicts, format="xml"):
149 for record_dict in record_dicts:
151 save_record_to_file(filename + "." + str(index), record_dict)
153 save_record_to_file(filename, record_dict)
155 elif format == "xmllist":
156 f = open(filename, "w")
157 f.write("<recordlist>\n")
158 for record_dict in record_dicts:
159 record_obj=Record(dict=record_dict)
160 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
161 f.write("</recordlist>\n")
163 elif format == "hrnlist":
164 f = open(filename, "w")
165 for record_dict in record_dicts:
166 record_obj=Record(dict=record_dict)
167 f.write(record_obj.hrn + "\n")
170 # this should never happen
171 print "unknown output format", format
173 def save_record_to_file(filename, record_dict):
174 record = Record(dict=record_dict)
175 xml = record.save_as_xml()
176 f=codecs.open(filename, encoding='utf-8',mode="w")
181 # minimally check a key argument
182 def check_ssh_key (key):
183 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
184 return re.match(good_ssh_key, key, re.IGNORECASE)
187 def normalize_type (type):
188 if type.startswith('au'):
190 elif type.startswith('us'):
192 elif type.startswith('sl'):
194 elif type.startswith('no'):
199 def load_record_from_opts(options):
201 if hasattr(options, 'type'):
202 options.type = normalize_type(options.type)
203 if hasattr(options, 'xrn') and options.xrn:
204 if hasattr(options, 'type') and options.type:
205 xrn = Xrn(options.xrn, options.type)
207 xrn = Xrn(options.xrn)
208 record_dict['urn'] = xrn.get_urn()
209 record_dict['hrn'] = xrn.get_hrn()
210 record_dict['type'] = xrn.get_type()
211 if hasattr(options, 'key') and options.key:
213 pubkey = open(options.key, 'r').read()
216 if not check_ssh_key (pubkey):
217 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
218 record_dict['reg-keys'] = [pubkey]
219 if hasattr(options, 'slices') and options.slices:
220 record_dict['slices'] = options.slices
221 if hasattr(options, 'reg_researchers') and options.reg_researchers is not None:
222 record_dict['reg-researchers'] = options.reg_researchers
223 if hasattr(options, 'email') and options.email:
224 record_dict['email'] = options.email
225 # authorities can have a name for standalone deployment
226 if hasattr(options, 'name') and options.name:
227 record_dict['name'] = options.name
228 if hasattr(options, 'reg_pis') and options.reg_pis:
229 record_dict['reg-pis'] = options.reg_pis
231 # handle extra settings
232 record_dict.update(options.extras)
234 return Record(dict=record_dict)
236 def load_record_from_file(filename):
237 f=codecs.open(filename, encoding="utf-8", mode="r")
238 xml_string = f.read()
240 return Record(xml=xml_string)
244 def unique_call_id(): return uuid.uuid4().urn
246 ########## a simple model for maintaing 3 doc attributes per command (instead of just one)
247 # essentially for the methods that implement a subcommand like sfi list
248 # we need to keep track of
249 # (*) doc a few lines that tell what it does, still located in __doc__
250 # (*) args_string a simple one-liner that describes mandatory arguments
251 # (*) example well, one or several releant examples
253 # since __doc__ only accounts for one, we use this simple mechanism below
254 # however we keep doc in place for easier migration
256 from functools import wraps
258 # we use a list as well as a dict so we can keep track of the order
262 def declare_command (args_string, example,aliases=None):
264 name=getattr(m,'__name__')
265 doc=getattr(m,'__doc__',"-- missing doc --")
266 doc=doc.strip(" \t\n")
267 commands_list.append(name)
268 # last item is 'canonical' name, so we can know which commands are aliases
269 command_tuple=(doc, args_string, example,name)
270 commands_dict[name]=command_tuple
271 if aliases is not None:
272 for alias in aliases:
273 commands_list.append(alias)
274 commands_dict[alias]=command_tuple
276 def new_method (*args, **kwds): return m(*args, **kwds)
281 def remove_none_fields (record):
282 none_fields=[ k for (k,v) in record.items() if v is None ]
283 for k in none_fields: del record[k]
289 # dirty hack to make this class usable from the outside
290 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
293 def default_sfi_dir ():
294 if os.path.isfile("./sfi_config"):
297 return os.path.expanduser("~/.sfi/")
299 # dummy to meet Sfi's expectations for its 'options' field
300 # i.e. s/t we can do setattr on
304 def __init__ (self,options=None):
305 if options is None: options=Sfi.DummyOptions()
306 for opt in Sfi.required_options:
307 if not hasattr(options,opt): setattr(options,opt,None)
308 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
309 self.options = options
311 self.authority = None
312 self.logger = sfi_logger
313 self.logger.enable_console()
314 ### various auxiliary material that we keep at hand
316 # need to call this other than just 'config' as we have a command/method with that name
317 self.config_instance=None
318 self.config_file=None
319 self.client_bootstrap=None
321 ### suitable if no reasonable command has been provided
322 def print_commands_help (self, options):
323 verbose=getattr(options,'verbose')
324 format3="%10s %-35s %s"
328 print format3%("command", "cmd_args", "description")
332 self.create_parser_global().print_help()
333 # preserve order from the code
334 for command in commands_list:
336 (doc, args_string, example, canonical) = commands_dict[command]
338 print "Cannot find info on command %s - skipped"%command
342 if command==canonical:
343 doc = doc.replace("\n", "\n" + format3offset * ' ')
344 print format3 % (command,args_string,doc)
346 self.create_parser_command(command).print_help()
348 print format3 % (command,"<<alias for %s>>"%canonical,"")
350 ### now if a known command was found we can be more verbose on that one
351 def print_help (self):
352 print "==================== Generic sfi usage"
353 self.sfi_parser.print_help()
354 (doc, _, example, canonical) = commands_dict[self.command]
355 if canonical != self.command:
356 print "\n==================== NOTE: {} is an alias for genuine {}"\
357 .format(self.command, canonical)
358 self.command = canonical
359 print "\n==================== Purpose of {}".format(self.command)
361 print "\n==================== Specific usage for {}".format(self.command)
362 self.command_parser.print_help()
364 print "\n==================== {} example(s)".format(self.command)
367 def create_parser_global(self):
368 # Generate command line parser
369 parser = OptionParser(add_help_option=False,
370 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
371 description="Commands: {}".format(" ".join(commands_list)))
372 parser.add_option("-r", "--registry", dest="registry",
373 help="root registry", metavar="URL", default=None)
374 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
375 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
376 parser.add_option("-R", "--raw", dest="raw", default=None,
377 help="Save raw, unparsed server response to a file")
378 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
379 help="raw file format ([text]|pickled|json)", default="text",
380 choices=("text","pickled","json"))
381 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
382 help="text string to write before and after raw output")
383 parser.add_option("-d", "--dir", dest="sfi_dir",
384 help="config & working directory - default is %default",
385 metavar="PATH", default=Sfi.default_sfi_dir())
386 parser.add_option("-u", "--user", dest="user",
387 help="user name", metavar="HRN", default=None)
388 parser.add_option("-a", "--auth", dest="auth",
389 help="authority name", metavar="HRN", default=None)
390 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
391 help="verbose mode - cumulative")
392 parser.add_option("-D", "--debug",
393 action="store_true", dest="debug", default=False,
394 help="Debug (xml-rpc) protocol messages")
395 # would it make sense to use ~/.ssh/id_rsa as a default here ?
396 parser.add_option("-k", "--private-key",
397 action="store", dest="user_private_key", default=None,
398 help="point to the private key file to use if not yet installed in sfi_dir")
399 parser.add_option("-t", "--timeout", dest="timeout", default=None,
400 help="Amout of time to wait before timing out the request")
401 parser.add_option("-h", "--help",
402 action="store_true", dest="help", default=False,
403 help="one page summary on commands & exit")
404 parser.disable_interspersed_args()
409 def create_parser_command(self, command):
410 if command not in commands_dict:
411 msg="Invalid command\n"
413 msg += ','.join(commands_list)
414 self.logger.critical(msg)
417 # retrieve args_string
418 (_, args_string, __,canonical) = commands_dict[command]
420 parser = OptionParser(add_help_option=False,
421 usage="sfi [sfi_options] {} [cmd_options] {}"\
422 .format(command, args_string))
423 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
424 help="Summary of one command usage")
426 if canonical in ("config"):
427 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
428 help='how myslice config variables as well')
430 if canonical in ("version"):
431 parser.add_option("-l","--local",
432 action="store_true", dest="version_local", default=False,
433 help="display version of the local client")
435 if canonical in ("version", "trusted"):
436 parser.add_option("-R","--registry_interface",
437 action="store_true", dest="registry_interface", default=False,
438 help="target the registry interface instead of slice interface")
440 if canonical in ("register", "update"):
441 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
442 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
443 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
444 parser.add_option('-n', '--name', dest='name', default="", help="name (optional for authorities)")
445 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
447 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
448 default='', type="str", action='callback', callback=optparse_listvalue_callback)
449 parser.add_option('-r', '--researchers', dest='reg_researchers', metavar='<researchers>',
450 help='Set/replace slice researchers - use -r none to reset', default=None, type="str", action='callback',
451 callback=optparse_listvalue_callback)
452 parser.add_option('-p', '--pis', dest='reg_pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
453 default='', type="str", action='callback', callback=optparse_listvalue_callback)
454 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
455 action="callback", callback=optparse_dictvalue_callback, nargs=1,
456 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
458 # user specifies remote aggregate/sm/component
459 if canonical in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
460 "action", "shutdown", "renew", "status"):
461 parser.add_option("-d", "--delegate", dest="delegate", default=None,
463 help="Include a credential delegated to the user's root"+\
464 "authority in set of credentials for this call")
466 # show_credential option
467 if canonical in ("list","resources", "describe", "provision", "allocate", "register","update","remove","delete","status","renew"):
468 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
469 help="show credential(s) used in human-readable form")
470 if canonical in ("renew"):
471 parser.add_option("-l","--as-long-as-possible",dest='alap',action='store_true',default=False,
472 help="renew as long as possible")
473 # registy filter option
474 if canonical in ("list", "show", "remove"):
475 parser.add_option("-t", "--type", dest="type", type="choice",
476 help="type filter ([all]|user|slice|authority|node|aggregate)",
477 choices=("all", "user", "slice", "authority", "node", "aggregate"),
479 if canonical in ("show"):
480 parser.add_option("-k","--key",dest="keys",action="append",default=[],
481 help="specify specific keys to be displayed from record")
482 parser.add_option("-n","--no-details",dest="no_details",action="store_true",default=False,
483 help="call Resolve without the 'details' option")
484 if canonical in ("resources", "describe"):
486 parser.add_option("-r", "--rspec-version", dest="rspec_version", default=DEFAULT_RSPEC_VERSION,
487 help="schema type and version of resulting RSpec (default:{})".format(DEFAULT_RSPEC_VERSION))
488 # disable/enable cached rspecs
489 parser.add_option("-c", "--current", dest="current", default=False,
491 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
493 parser.add_option("-f", "--format", dest="format", type="choice",
494 help="display format ([xml]|dns|ip)", default="xml",
495 choices=("xml", "dns", "ip"))
496 #panos: a new option to define the type of information about resources a user is interested in
497 parser.add_option("-i", "--info", dest="info",
498 help="optional component information", default=None)
499 # a new option to retreive or not reservation-oriented RSpecs (leases)
500 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
501 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
502 choices=("all", "resources", "leases"), default="resources")
505 if canonical in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
506 parser.add_option("-o", "--output", dest="file",
507 help="output XML to file", metavar="FILE", default=None)
509 if canonical in ("show", "list"):
510 parser.add_option("-f", "--format", dest="format", type="choice",
511 help="display format ([text]|xml)", default="text",
512 choices=("text", "xml"))
514 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
515 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
516 choices=("xml", "xmllist", "hrnlist"))
517 if canonical == 'list':
518 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
519 help="list all child records", default=False)
520 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
521 help="gives details, like user keys", default=False)
522 if canonical in ("delegate"):
523 parser.add_option("-u", "--user",
524 action="store_true", dest="delegate_user", default=False,
525 help="delegate your own credentials; default if no other option is provided")
526 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
527 metavar="slice_hrn", help="delegate cred. for slice HRN")
528 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
529 metavar='auth_hrn', help="delegate cred for auth HRN")
530 # this primarily is a shorthand for -A my_hrn^
531 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
532 help="delegate your PI credentials, so s.t. like -A your_hrn^")
533 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
534 help="""by default the mandatory argument is expected to be a user,
535 use this if you mean an authority instead""")
537 if canonical in ("myslice"):
538 parser.add_option("-p","--password",dest='password',action='store',default=None,
539 help="specify mainfold password on the command line")
540 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
541 metavar="slice_hrn", help="delegate cred. for slice HRN")
542 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
543 metavar='auth_hrn', help="delegate PI cred for auth HRN")
544 parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
545 parser.add_option('-b', '--backend', dest='backend', help="Override 'backend' from the config file")
551 # Main: parse arguments and dispatch to command
553 def dispatch(self, command, command_options, command_args):
554 (doc, args_string, example, canonical) = commands_dict[command]
555 method=getattr(self, canonical, None)
557 print "sfi: unknown command {}".format(command)
558 raise SystemExit("Unknown command {}".format(command))
559 return method(command_options, command_args)
562 self.sfi_parser = self.create_parser_global()
563 (options, args) = self.sfi_parser.parse_args()
565 self.print_commands_help(options)
567 self.options = options
569 self.logger.setLevelFromOptVerbose(self.options.verbose)
572 self.logger.critical("No command given. Use -h for help.")
573 self.print_commands_help(options)
576 # complete / find unique match with command set
577 command_candidates = Candidates (commands_list)
579 command = command_candidates.only_match(input)
581 self.print_commands_help(options)
583 # second pass options parsing
585 self.command_parser = self.create_parser_command(command)
586 (command_options, command_args) = self.command_parser.parse_args(args[1:])
587 if command_options.help:
590 self.command_options = command_options
594 self.logger.debug("Command={}".format(self.command))
597 retcod = self.dispatch(command, command_options, command_args)
601 self.logger.log_exc ("sfi command {} failed".format(command))
606 def read_config(self):
607 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
608 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
610 if Config.is_ini(config_file):
611 config = Config (config_file)
613 # try upgrading from shell config format
614 fp, fn = mkstemp(suffix='sfi_config', text=True)
616 # we need to preload the sections we want parsed
617 # from the shell config
618 config.add_section('sfi')
619 # sface users should be able to use this same file to configure their stuff
620 config.add_section('sface')
621 # manifold users should be able to specify the details
622 # of their backend server here for 'sfi myslice'
623 config.add_section('myslice')
624 config.load(config_file)
626 shutil.move(config_file, shell_config_file)
628 config.save(config_file)
631 self.logger.critical("Failed to read configuration file {}".format(config_file))
632 self.logger.info("Make sure to remove the export clauses and to add quotes")
633 if self.options.verbose==0:
634 self.logger.info("Re-run with -v for more details")
636 self.logger.log_exc("Could not read config file {}".format(config_file))
639 self.config_instance=config
642 if (self.options.sm is not None):
643 self.sm_url = self.options.sm
644 elif hasattr(config, "SFI_SM"):
645 self.sm_url = config.SFI_SM
647 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in {}".format(config_file))
651 if (self.options.registry is not None):
652 self.reg_url = self.options.registry
653 elif hasattr(config, "SFI_REGISTRY"):
654 self.reg_url = config.SFI_REGISTRY
656 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in {}".format(config_file))
660 if (self.options.user is not None):
661 self.user = self.options.user
662 elif hasattr(config, "SFI_USER"):
663 self.user = config.SFI_USER
665 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in {}".format(config_file))
669 if (self.options.auth is not None):
670 self.authority = self.options.auth
671 elif hasattr(config, "SFI_AUTH"):
672 self.authority = config.SFI_AUTH
674 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in {}".format(config_file))
677 self.config_file=config_file
682 # Get various credential and spec files
684 # Establishes limiting conventions
685 # - conflates MAs and SAs
686 # - assumes last token in slice name is unique
688 # Bootstraps credentials
689 # - bootstrap user credential from self-signed certificate
690 # - bootstrap authority credential from user credential
691 # - bootstrap slice credential from user credential
694 # init self-signed cert, user credentials and gid
695 def bootstrap (self):
696 if self.options.verbose:
697 self.logger.info("Initializing SfaClientBootstrap with {}".format(self.reg_url))
698 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
700 # if -k is provided, use this to initialize private key
701 if self.options.user_private_key:
702 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
704 # trigger legacy compat code if needed
705 # the name has changed from just <leaf>.pkey to <hrn>.pkey
706 if not os.path.isfile(client_bootstrap.private_key_filename()):
707 self.logger.info ("private key not found, trying legacy name")
709 legacy_private_key = os.path.join (self.options.sfi_dir, "{}.pkey"
710 .format(Xrn.unescape(get_leaf(self.user))))
711 self.logger.debug("legacy_private_key={}"
712 .format(legacy_private_key))
713 client_bootstrap.init_private_key_if_missing (legacy_private_key)
714 self.logger.info("Copied private key from legacy location {}"
715 .format(legacy_private_key))
717 self.logger.log_exc("Can't find private key ")
721 client_bootstrap.bootstrap_my_gid()
722 # extract what's needed
723 self.private_key = client_bootstrap.private_key()
724 self.my_credential_string = client_bootstrap.my_credential_string ()
725 self.my_credential = {'geni_type': 'geni_sfa',
727 'geni_value': self.my_credential_string}
728 self.my_gid = client_bootstrap.my_gid ()
729 self.client_bootstrap = client_bootstrap
732 def my_authority_credential_string(self):
733 if not self.authority:
734 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
736 return self.client_bootstrap.authority_credential_string (self.authority)
738 def authority_credential_string(self, auth_hrn):
739 return self.client_bootstrap.authority_credential_string (auth_hrn)
741 def slice_credential_string(self, name):
742 return self.client_bootstrap.slice_credential_string (name)
744 def slice_credential(self, name):
745 return {'geni_type': 'geni_sfa',
747 'geni_value': self.slice_credential_string(name)}
749 # xxx should be supported by sfaclientbootstrap as well
750 def delegate_cred(self, object_cred, hrn, type='authority'):
751 # the gid and hrn of the object we are delegating
752 if isinstance(object_cred, str):
753 object_cred = Credential(string=object_cred)
754 object_gid = object_cred.get_gid_object()
755 object_hrn = object_gid.get_hrn()
757 if not object_cred.get_privileges().get_all_delegate():
758 self.logger.error("Object credential {} does not have delegate bit set"
762 # the delegating user's gid
763 caller_gidfile = self.my_gid()
765 # the gid of the user who will be delegated to
766 delegee_gid = self.client_bootstrap.gid(hrn,type)
767 delegee_hrn = delegee_gid.get_hrn()
768 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
769 return dcred.save_to_string(save_parents=True)
772 # Management of the servers
777 if not hasattr (self, 'registry_proxy'):
778 self.logger.info("Contacting Registry at: {}".format(self.reg_url))
779 self.registry_proxy \
780 = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
781 timeout=self.options.timeout, verbose=self.options.debug)
782 return self.registry_proxy
786 if not hasattr (self, 'sliceapi_proxy'):
787 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
788 if hasattr(self.command_options,'component') and self.command_options.component:
789 # resolve the hrn at the registry
790 node_hrn = self.command_options.component
791 records = self.registry().Resolve(node_hrn, self.my_credential_string)
792 records = filter_records('node', records)
794 self.logger.warning("No such component:{}".format(opts.component))
796 cm_url = "http://{}:{}/".format(record['hostname'], CM_PORT)
797 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
799 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
800 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
801 self.sm_url = 'http://' + self.sm_url
802 self.logger.info("Contacting Slice Manager at: {}".format(self.sm_url))
803 self.sliceapi_proxy \
804 = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
805 timeout=self.options.timeout, verbose=self.options.debug)
806 return self.sliceapi_proxy
808 def get_cached_server_version(self, server):
809 # check local cache first
812 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
813 cache_key = server.url + "-version"
815 cache = Cache(cache_file)
818 self.logger.info("Local cache not found at: {}".format(cache_file))
821 version = cache.get(cache_key)
824 result = server.GetVersion()
825 version= ReturnValue.get_value(result)
826 # cache version for 20 minutes
827 cache.add(cache_key, version, ttl= 60*20)
828 self.logger.info("Updating cache file {}".format(cache_file))
829 cache.save_to_file(cache_file)
833 ### resurrect this temporarily so we can support V1 aggregates for a while
834 def server_supports_options_arg(self, server):
836 Returns true if server support the optional call_id arg, false otherwise.
838 server_version = self.get_cached_server_version(server)
840 # xxx need to rewrite this
841 if int(server_version.get('geni_api')) >= 2:
845 def server_supports_call_id_arg(self, server):
846 server_version = self.get_cached_server_version(server)
848 if 'sfa' in server_version and 'code_tag' in server_version:
849 code_tag = server_version['code_tag']
850 code_tag_parts = code_tag.split("-")
851 version_parts = code_tag_parts[0].split(".")
852 major, minor = version_parts[0], version_parts[1]
853 rev = code_tag_parts[1]
854 if int(major) == 1 and minor == 0 and build >= 22:
858 ### ois = options if supported
859 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
860 def ois (self, server, option_dict):
861 if self.server_supports_options_arg (server):
863 elif self.server_supports_call_id_arg (server):
864 return [ unique_call_id () ]
868 ### cis = call_id if supported - like ois
869 def cis (self, server):
870 if self.server_supports_call_id_arg (server):
871 return [ unique_call_id ]
875 ######################################## miscell utilities
876 def get_rspec_file(self, rspec):
877 if (os.path.isabs(rspec)):
880 file = os.path.join(self.options.sfi_dir, rspec)
881 if (os.path.isfile(file)):
884 self.logger.critical("No such rspec file {}".format(rspec))
887 def get_record_file(self, record):
888 if (os.path.isabs(record)):
891 file = os.path.join(self.options.sfi_dir, record)
892 if (os.path.isfile(file)):
895 self.logger.critical("No such registry record file {}".format(record))
899 # helper function to analyze raw output
900 # for main : return 0 if everything is fine, something else otherwise (mostly 1 for now)
901 def success (self, raw):
902 return_value=ReturnValue (raw)
903 output=ReturnValue.get_output(return_value)
904 # means everything is fine
907 # something went wrong
908 print 'ERROR:',output
911 #==========================================================================
912 # Following functions implement the commands
914 # Registry-related commands
915 #==========================================================================
917 @declare_command("","")
918 def config (self, options, args):
919 "Display contents of current config"
920 print "# From configuration file {}".format(self.config_file)
921 flags=[ ('sfi', [ ('registry','reg_url'),
922 ('auth','authority'),
928 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
930 for (section, tuples) in flags:
931 print "[{}]".format(section)
933 for (external_name, internal_name) in tuples:
934 print "{:-20} = {}".format(external_name, getattr(self, internal_name))
937 varname = "{}_{}".format(section.upper(), name.upper())
938 value = getattr(self.config_instance,varname)
939 print "{:-20} = {}".format(name, value)
940 # xxx should analyze result
943 @declare_command("","")
944 def version(self, options, args):
946 display an SFA server version (GetVersion)
947 or version information about sfi itself
949 if options.version_local:
950 version=version_core()
952 if options.registry_interface:
953 server=self.registry()
955 server = self.sliceapi()
956 result = server.GetVersion()
957 version = ReturnValue.get_value(result)
959 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
961 pprinter = PrettyPrinter(indent=4)
962 pprinter.pprint(version)
963 # xxx should analyze result
966 @declare_command("authority","")
967 def list(self, options, args):
969 list entries in named authority registry (List)
976 if options.recursive:
977 opts['recursive'] = options.recursive
979 if options.show_credential:
980 show_credentials(self.my_credential_string)
982 list = self.registry().List(hrn, self.my_credential_string, options)
984 raise Exception, "Not enough parameters for the 'list' command"
986 # filter on person, slice, site, node, etc.
987 # This really should be in the self.filter_records funct def comment...
988 list = filter_records(options.type, list)
989 terminal_render (list, options)
991 save_records_to_file(options.file, list, options.fileformat)
992 # xxx should analyze result
995 @declare_command("name","")
996 def show(self, options, args):
998 show details about named registry record (Resolve)
1004 # explicitly require Resolve to run in details mode
1006 if not options.no_details: resolve_options['details']=True
1007 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, resolve_options)
1008 record_dicts = filter_records(options.type, record_dicts)
1009 if not record_dicts:
1010 self.logger.error("No record of type {}".format(options.type))
1012 # user has required to focus on some keys
1014 def project (record):
1016 for key in options.keys:
1017 try: projected[key]=record[key]
1020 record_dicts = [ project (record) for record in record_dicts ]
1021 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
1022 for record in records:
1023 if (options.format == "text"): record.dump(sort=True)
1024 else: print record.save_as_xml()
1026 save_records_to_file(options.file, record_dicts, options.fileformat)
1027 # xxx should analyze result
1030 # this historically was named 'add', it is now 'register' with an alias for legacy
1031 @declare_command("[xml-filename]","",['add'])
1032 def register(self, options, args):
1033 """create new record in registry (Register)
1034 from command line options (recommended)
1035 old-school method involving an xml file still supported"""
1037 auth_cred = self.my_authority_credential_string()
1038 if options.show_credential:
1039 show_credentials(auth_cred)
1046 record_filepath = args[0]
1047 rec_file = self.get_record_file(record_filepath)
1048 record_dict.update(load_record_from_file(rec_file).todict())
1050 print "Cannot load record file {}".format(record_filepath)
1053 record_dict.update(load_record_from_opts(options).todict())
1054 # we should have a type by now
1055 if 'type' not in record_dict :
1058 # this is still planetlab dependent.. as plc will whine without that
1059 # also, it's only for adding
1060 if record_dict['type'] == 'user':
1061 if not 'first_name' in record_dict:
1062 record_dict['first_name'] = record_dict['hrn']
1063 if 'last_name' not in record_dict:
1064 record_dict['last_name'] = record_dict['hrn']
1065 register = self.registry().Register(record_dict, auth_cred)
1066 # xxx looks like the result here is not ReturnValue-compatible
1067 #return self.success (register)
1068 # xxx should analyze result
1071 @declare_command("[xml-filename]","")
1072 def update(self, options, args):
1073 """update record into registry (Update)
1074 from command line options (recommended)
1075 old-school method involving an xml file still supported"""
1078 record_filepath = args[0]
1079 rec_file = self.get_record_file(record_filepath)
1080 record_dict.update(load_record_from_file(rec_file).todict())
1082 record_dict.update(load_record_from_opts(options).todict())
1083 # at the very least we need 'type' here
1084 if 'type' not in record_dict or record_dict['type'] is None:
1088 # don't translate into an object, as this would possibly distort
1089 # user-provided data; e.g. add an 'email' field to Users
1090 if record_dict['type'] in ['user']:
1091 if record_dict['hrn'] == self.user:
1092 cred = self.my_credential_string
1094 cred = self.my_authority_credential_string()
1095 elif record_dict['type'] in ['slice']:
1097 cred = self.slice_credential_string(record_dict['hrn'])
1098 except ServerException, e:
1099 # XXX smbaker -- once we have better error return codes, update this
1100 # to do something better than a string compare
1101 if "Permission error" in e.args[0]:
1102 cred = self.my_authority_credential_string()
1105 elif record_dict['type'] in ['authority']:
1106 cred = self.my_authority_credential_string()
1107 elif record_dict['type'] in ['node']:
1108 cred = self.my_authority_credential_string()
1110 raise Exception("unknown record type {}".format(record_dict['type']))
1111 if options.show_credential:
1112 show_credentials(cred)
1113 update = self.registry().Update(record_dict, cred)
1114 # xxx looks like the result here is not ReturnValue-compatible
1115 #return self.success(update)
1116 # xxx should analyze result
1119 @declare_command("hrn","")
1120 def remove(self, options, args):
1121 "remove registry record by name (Remove)"
1122 auth_cred = self.my_authority_credential_string()
1130 if options.show_credential:
1131 show_credentials(auth_cred)
1132 remove = self.registry().Remove(hrn, auth_cred, type)
1133 # xxx looks like the result here is not ReturnValue-compatible
1134 #return self.success (remove)
1135 # xxx should analyze result
1138 # ==================================================================
1139 # Slice-related commands
1140 # ==================================================================
1142 # show rspec for named slice
1143 @declare_command("","",['discover'])
1144 def resources(self, options, args):
1146 discover available resources (ListResources)
1148 server = self.sliceapi()
1151 creds = [self.my_credential]
1152 if options.delegate:
1153 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1154 if options.show_credential:
1155 show_credentials(creds)
1157 # no need to check if server accepts the options argument since the options has
1158 # been a required argument since v1 API
1160 # always send call_id to v2 servers
1161 api_options ['call_id'] = unique_call_id()
1162 # ask for cached value if available
1163 api_options ['cached'] = True
1165 api_options['info'] = options.info
1166 if options.list_leases:
1167 api_options['list_leases'] = options.list_leases
1169 if options.current == True:
1170 api_options['cached'] = False
1172 api_options['cached'] = True
1173 if options.rspec_version:
1174 version_manager = VersionManager()
1175 server_version = self.get_cached_server_version(server)
1176 if 'sfa' in server_version:
1177 # just request the version the client wants
1178 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1180 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1182 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1183 list_resources = server.ListResources (creds, api_options)
1184 value = ReturnValue.get_value(list_resources)
1185 if self.options.raw:
1186 save_raw_to_file(list_resources, self.options.raw, self.options.rawformat, self.options.rawbanner)
1187 if options.file is not None:
1188 save_rspec_to_file(value, options.file)
1189 if (self.options.raw is None) and (options.file is None):
1190 display_rspec(value, options.format)
1191 return self.success(list_resources)
1193 @declare_command("slice_hrn","")
1194 def describe(self, options, args):
1196 shows currently allocated/provisioned resources
1197 of the named slice or set of slivers (Describe)
1199 server = self.sliceapi()
1202 creds = [self.slice_credential(args[0])]
1203 if options.delegate:
1204 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1205 if options.show_credential:
1206 show_credentials(creds)
1208 api_options = {'call_id': unique_call_id(),
1210 'info': options.info,
1211 'list_leases': options.list_leases,
1212 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1215 api_options['info'] = options.info
1217 if options.rspec_version:
1218 version_manager = VersionManager()
1219 server_version = self.get_cached_server_version(server)
1220 if 'sfa' in server_version:
1221 # just request the version the client wants
1222 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1224 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1225 urn = Xrn(args[0], type='slice').get_urn()
1226 remove_none_fields(api_options)
1227 describe = server.Describe([urn], creds, api_options)
1228 value = ReturnValue.get_value(describe)
1229 if self.options.raw:
1230 save_raw_to_file(describe, self.options.raw, self.options.rawformat, self.options.rawbanner)
1231 if options.file is not None:
1232 save_rspec_to_file(value['geni_rspec'], options.file)
1233 if (self.options.raw is None) and (options.file is None):
1234 display_rspec(value['geni_rspec'], options.format)
1235 return self.success (describe)
1237 @declare_command("slice_hrn [<sliver_urn>...]","")
1238 def delete(self, options, args):
1240 de-allocate and de-provision all or named slivers of the named slice (Delete)
1242 server = self.sliceapi()
1246 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1249 # we have sliver urns
1250 sliver_urns = args[1:]
1252 # we provision all the slivers of the slice
1253 sliver_urns = [slice_urn]
1256 slice_cred = self.slice_credential(slice_hrn)
1257 creds = [slice_cred]
1259 # options and call_id when supported
1261 api_options ['call_id'] = unique_call_id()
1262 if options.show_credential:
1263 show_credentials(creds)
1264 delete = server.Delete(sliver_urns, creds, *self.ois(server, api_options ) )
1265 value = ReturnValue.get_value(delete)
1266 if self.options.raw:
1267 save_raw_to_file(delete, self.options.raw, self.options.rawformat, self.options.rawbanner)
1270 return self.success (delete)
1272 @declare_command("slice_hrn rspec","")
1273 def allocate(self, options, args):
1275 allocate resources to the named slice (Allocate)
1277 server = self.sliceapi()
1278 server_version = self.get_cached_server_version(server)
1280 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1283 creds = [self.slice_credential(slice_hrn)]
1285 delegated_cred = None
1286 if server_version.get('interface') == 'slicemgr':
1287 # delegate our cred to the slice manager
1288 # do not delegate cred to slicemgr...not working at the moment
1290 #if server_version.get('hrn'):
1291 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1292 #elif server_version.get('urn'):
1293 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1295 if options.show_credential:
1296 show_credentials(creds)
1299 rspec_file = self.get_rspec_file(args[1])
1300 rspec = open(rspec_file).read()
1302 api_options ['call_id'] = unique_call_id()
1306 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1307 remove_none_fields(slice_records[0])
1308 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1309 slice_record = slice_records[0]
1310 user_hrns = slice_record['reg-researchers']
1311 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1312 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1313 sfa_users = sfa_users_arg(user_records, slice_record)
1314 geni_users = pg_users_arg(user_records)
1316 api_options['sfa_users'] = sfa_users
1317 api_options['geni_users'] = geni_users
1319 allocate = server.Allocate(slice_urn, creds, rspec, api_options)
1320 value = ReturnValue.get_value(allocate)
1321 if self.options.raw:
1322 save_raw_to_file(allocate, self.options.raw, self.options.rawformat, self.options.rawbanner)
1323 if options.file is not None:
1324 save_rspec_to_file (value['geni_rspec'], options.file)
1325 if (self.options.raw is None) and (options.file is None):
1327 return self.success(allocate)
1329 @declare_command("slice_hrn [<sliver_urn>...]","")
1330 def provision(self, options, args):
1332 provision all or named already allocated slivers of the named slice (Provision)
1334 server = self.sliceapi()
1335 server_version = self.get_cached_server_version(server)
1337 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1339 # we have sliver urns
1340 sliver_urns = args[1:]
1342 # we provision all the slivers of the slice
1343 sliver_urns = [slice_urn]
1346 creds = [self.slice_credential(slice_hrn)]
1347 delegated_cred = None
1348 if server_version.get('interface') == 'slicemgr':
1349 # delegate our cred to the slice manager
1350 # do not delegate cred to slicemgr...not working at the moment
1352 #if server_version.get('hrn'):
1353 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1354 #elif server_version.get('urn'):
1355 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1357 if options.show_credential:
1358 show_credentials(creds)
1361 api_options ['call_id'] = unique_call_id()
1363 # set the requtested rspec version
1364 version_manager = VersionManager()
1365 rspec_version = version_manager._get_version('geni', '3').to_dict()
1366 api_options['geni_rspec_version'] = rspec_version
1369 # need to pass along user keys to the aggregate.
1371 # { urn: urn:publicid:IDN+emulab.net+user+alice
1372 # keys: [<ssh key A>, <ssh key B>]
1375 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1376 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1377 slice_record = slice_records[0]
1378 user_hrns = slice_record['reg-researchers']
1379 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1380 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1381 users = pg_users_arg(user_records)
1383 api_options['geni_users'] = users
1384 provision = server.Provision(sliver_urns, creds, api_options)
1385 value = ReturnValue.get_value(provision)
1386 if self.options.raw:
1387 save_raw_to_file(provision, self.options.raw, self.options.rawformat, self.options.rawbanner)
1388 if options.file is not None:
1389 save_rspec_to_file (value['geni_rspec'], options.file)
1390 if (self.options.raw is None) and (options.file is None):
1392 return self.success(provision)
1394 @declare_command("slice_hrn","")
1395 def status(self, options, args):
1397 retrieve the status of the slivers belonging to the named slice (Status)
1399 server = self.sliceapi()
1403 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1406 slice_cred = self.slice_credential(slice_hrn)
1407 creds = [slice_cred]
1409 # options and call_id when supported
1411 api_options['call_id']=unique_call_id()
1412 if options.show_credential:
1413 show_credentials(creds)
1414 status = server.Status([slice_urn], creds, *self.ois(server,api_options))
1415 value = ReturnValue.get_value(status)
1416 if self.options.raw:
1417 save_raw_to_file(status, self.options.raw, self.options.rawformat, self.options.rawbanner)
1420 return self.success (status)
1422 @declare_command("slice_hrn [<sliver_urn>...] action","")
1423 def action(self, options, args):
1425 Perform the named operational action on all or named slivers of the named slice
1427 server = self.sliceapi()
1431 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1433 # we have sliver urns
1434 sliver_urns = args[1:-1]
1436 # we provision all the slivers of the slice
1437 sliver_urns = [slice_urn]
1440 slice_cred = self.slice_credential(args[0])
1441 creds = [slice_cred]
1442 if options.delegate:
1443 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1444 creds.append(delegated_cred)
1446 perform_action = server.PerformOperationalAction(sliver_urns, creds, action , api_options)
1447 value = ReturnValue.get_value(perform_action)
1448 if self.options.raw:
1449 save_raw_to_file(perform_action, self.options.raw, self.options.rawformat, self.options.rawbanner)
1452 return self.success (perform_action)
1454 @declare_command("slice_hrn [<sliver_urn>...] time",
1455 "\n".join(["sfi renew onelab.ple.heartbeat 2015-04-31",
1456 "sfi renew onelab.ple.heartbeat 2015-04-31T14:00:00Z",
1457 "sfi renew onelab.ple.heartbeat +5d",
1458 "sfi renew onelab.ple.heartbeat +3w",
1459 "sfi renew onelab.ple.heartbeat +2m",]))
1460 def renew(self, options, args):
1464 server = self.sliceapi()
1469 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1472 # we have sliver urns
1473 sliver_urns = args[1:-1]
1475 # we provision all the slivers of the slice
1476 sliver_urns = [slice_urn]
1477 input_time = args[-1]
1479 # time: don't try to be smart on the time format, server-side will
1481 slice_cred = self.slice_credential(args[0])
1482 creds = [slice_cred]
1483 # options and call_id when supported
1485 api_options['call_id']=unique_call_id()
1487 api_options['geni_extend_alap']=True
1488 if options.show_credential:
1489 show_credentials(creds)
1490 renew = server.Renew(sliver_urns, creds, input_time, *self.ois(server,api_options))
1491 value = ReturnValue.get_value(renew)
1492 if self.options.raw:
1493 save_raw_to_file(renew, self.options.raw, self.options.rawformat, self.options.rawbanner)
1496 return self.success(renew)
1498 @declare_command("slice_hrn","")
1499 def shutdown(self, options, args):
1501 shutdown named slice (Shutdown)
1503 server = self.sliceapi()
1506 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1508 slice_cred = self.slice_credential(slice_hrn)
1509 creds = [slice_cred]
1510 shutdown = server.Shutdown(slice_urn, creds)
1511 value = ReturnValue.get_value(shutdown)
1512 if self.options.raw:
1513 save_raw_to_file(shutdown, self.options.raw, self.options.rawformat, self.options.rawbanner)
1516 return self.success (shutdown)
1518 @declare_command("[name]","")
1519 def gid(self, options, args):
1521 Create a GID (CreateGid)
1526 target_hrn = args[0]
1527 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1528 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1530 filename = options.file
1532 filename = os.sep.join([self.options.sfi_dir, '{}.gid'.format(target_hrn)])
1533 self.logger.info("writing {} gid to {}".format(target_hrn, filename))
1534 GID(string=gid).save_to_file(filename)
1535 # xxx should analyze result
1538 ####################
1539 @declare_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1541 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1542 the set of credentials in the scope for this call would be
1543 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1545 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1547 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1548 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1549 because of the two -s options
1552 def delegate (self, options, args):
1554 (locally) create delegate credential for use by given hrn
1555 make sure to check for 'sfi myslice' instead if you plan
1562 # support for several delegations in the same call
1563 # so first we gather the things to do
1565 for slice_hrn in options.delegate_slices:
1566 message = "{}.slice".format(slice_hrn)
1567 original = self.slice_credential_string(slice_hrn)
1568 tuples.append ( (message, original,) )
1569 if options.delegate_pi:
1570 my_authority=self.authority
1571 message = "{}.pi".format(my_authority)
1572 original = self.my_authority_credential_string()
1573 tuples.append ( (message, original,) )
1574 for auth_hrn in options.delegate_auths:
1575 message = "{}.auth".format(auth_hrn)
1576 original = self.authority_credential_string(auth_hrn)
1577 tuples.append ( (message, original, ) )
1578 # if nothing was specified at all at this point, let's assume -u
1579 if not tuples: options.delegate_user=True
1581 if options.delegate_user:
1582 message = "{}.user".format(self.user)
1583 original = self.my_credential_string
1584 tuples.append ( (message, original, ) )
1586 # default type for beneficial is user unless -A
1587 if options.delegate_to_authority: to_type='authority'
1588 else: to_type='user'
1590 # let's now handle all this
1591 # it's all in the filenaming scheme
1592 for (message,original) in tuples:
1593 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1594 delegated_credential = Credential (string=delegated_string)
1595 filename = os.path.join(self.options.sfi_dir,
1596 "{}_for_{}.{}.cred".format(message, to_hrn, to_type))
1597 delegated_credential.save_to_file(filename, save_parents=True)
1598 self.logger.info("delegated credential for {} to {} and wrote to {}"
1599 .format(message, to_hrn, filename))
1601 ####################
1602 @declare_command("","""$ less +/myslice sfi_config
1604 backend = http://manifold.pl.sophia.inria.fr:7080
1605 # the HRN that myslice uses, so that we are delegating to
1606 delegate = ple.upmc.slicebrowser
1607 # platform - this is a myslice concept
1609 # username - as of this writing (May 2013) a simple login name
1613 will first collect the slices that you are part of, then make sure
1614 all your credentials are up-to-date (read: refresh expired ones)
1615 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1616 and upload them all on myslice backend, using 'platform' and 'user'.
1617 A password will be prompted for the upload part.
1619 $ sfi -v myslice -- or sfi -vv myslice
1620 same but with more and more verbosity
1622 $ sfi m -b http://mymanifold.foo.com:7080/
1623 is synonym to sfi myslice as no other command starts with an 'm'
1624 and uses a custom backend for this one call
1627 def myslice (self, options, args):
1629 """ This helper is for refreshing your credentials at myslice; it will
1630 * compute all the slices that you currently have credentials on
1631 * refresh all your credentials (you as a user and pi, your slices)
1632 * upload them to the manifold backend server
1633 for last phase, sfi_config is read to look for the [myslice] section,
1634 and namely the 'backend', 'delegate' and 'user' settings"""
1640 # enable info by default
1641 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1642 ### the rough sketch goes like this
1643 # (0) produce a p12 file
1644 self.client_bootstrap.my_pkcs12()
1646 # (a) rain check for sufficient config in sfi_config
1648 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1649 for key in myslice_keys:
1651 # oct 2013 - I'm finding myself juggling with config files
1652 # so a couple of command-line options can now override config
1653 if hasattr(options,key) and getattr(options,key) is not None:
1654 value=getattr(options,key)
1656 full_key="MYSLICE_" + key.upper()
1657 value=getattr(self.config_instance,full_key,None)
1659 myslice_dict[key]=value
1661 print "Unsufficient config, missing key {} in [myslice] section of sfi_config"\
1663 if len(myslice_dict) != len(myslice_keys):
1666 # (b) figure whether we are PI for the authority where we belong
1667 self.logger.info("Resolving our own id {}".format(self.user))
1668 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1669 if len(my_records) != 1:
1670 print "Cannot Resolve {} -- exiting".format(self.user)
1672 my_record = my_records[0]
1673 my_auths_all = my_record['reg-pi-authorities']
1674 self.logger.info("Found {} authorities that we are PI for".format(len(my_auths_all)))
1675 self.logger.debug("They are {}".format(my_auths_all))
1677 my_auths = my_auths_all
1678 if options.delegate_auths:
1679 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1680 self.logger.debug("Restricted to user-provided auths {}".format(my_auths))
1682 # (c) get the set of slices that we are in
1683 my_slices_all=my_record['reg-slices']
1684 self.logger.info("Found {} slices that we are member of".format(len(my_slices_all)))
1685 self.logger.debug("They are: {}".format(my_slices_all))
1687 my_slices = my_slices_all
1688 # if user provided slices, deal only with these - if they are found
1689 if options.delegate_slices:
1690 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1691 self.logger.debug("Restricted to user-provided slices: {}".format(my_slices))
1693 # (d) make sure we have *valid* credentials for all these
1695 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1696 for auth_hrn in my_auths:
1697 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1698 for slice_hrn in my_slices:
1699 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1701 # (e) check for the delegated version of these
1702 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1703 # switch to myslice using an authority instead of a user
1704 delegatee_type='user'
1705 delegatee_hrn=myslice_dict['delegate']
1706 hrn_delegated_credentials = []
1707 for (hrn, htype, credential) in hrn_credentials:
1708 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1709 # save these so user can monitor what she's uploaded
1710 filename = os.path.join ( self.options.sfi_dir,
1711 "{}.{}_for_{}.{}.cred"\
1712 .format(hrn, htype, delegatee_hrn, delegatee_type))
1713 with file(filename,'w') as f:
1714 f.write(delegated_credential)
1715 self.logger.debug("(Over)wrote {}".format(filename))
1716 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1718 # (f) and finally upload them to manifold server
1719 # xxx todo add an option so the password can be set on the command line
1720 # (but *NOT* in the config file) so other apps can leverage this
1721 self.logger.info("Uploading on backend at {}".format(myslice_dict['backend']))
1722 uploader = ManifoldUploader (logger=self.logger,
1723 url=myslice_dict['backend'],
1724 platform=myslice_dict['platform'],
1725 username=myslice_dict['username'],
1726 password=options.password)
1727 uploader.prompt_all()
1728 (count_all,count_success)=(0,0)
1729 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1731 inspect=Credential(string=delegated_credential)
1732 expire_datetime=inspect.get_expiration()
1733 message="{} ({}) [exp:{}]".format(hrn, htype, expire_datetime)
1734 if uploader.upload(delegated_credential,message=message):
1737 self.logger.info("Successfully uploaded {}/{} credentials"
1738 .format(count_success, count_all))
1740 # at first I thought we would want to save these,
1741 # like 'sfi delegate does' but on second thought
1742 # it is probably not helpful as people would not
1743 # need to run 'sfi delegate' at all anymore
1744 if count_success != count_all: sys.exit(1)
1745 # xxx should analyze result
1748 @declare_command("cred","")
1749 def trusted(self, options, args):
1751 return the trusted certs at this interface (get_trusted_certs)
1753 if options.registry_interface:
1754 server=self.registry()
1756 server = self.sliceapi()
1757 cred = self.my_authority_credential_string()
1758 trusted_certs = server.get_trusted_certs(cred)
1759 if not options.registry_interface:
1760 trusted_certs = ReturnValue.get_value(trusted_certs)
1762 for trusted_cert in trusted_certs:
1763 print "\n===========================================================\n"
1764 gid = GID(string=trusted_cert)
1766 cert = Certificate(string=trusted_cert)
1767 self.logger.debug('Sfi.trusted -> {}'.format(cert.get_subject()))
1768 print "Certificate:\n{}\n\n".format(trusted_cert)
1769 # xxx should analyze result