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 retrieve or not reservation-oriented RSpecs (leases)
500 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
501 help="Retrieve 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 for arg in command_args:
560 if 'help' in arg or arg == '-h':
563 return method(command_options, command_args)
566 self.sfi_parser = self.create_parser_global()
567 (options, args) = self.sfi_parser.parse_args()
569 self.print_commands_help(options)
571 self.options = options
573 self.logger.setLevelFromOptVerbose(self.options.verbose)
576 self.logger.critical("No command given. Use -h for help.")
577 self.print_commands_help(options)
580 # complete / find unique match with command set
581 command_candidates = Candidates (commands_list)
583 command = command_candidates.only_match(input)
585 self.print_commands_help(options)
587 # second pass options parsing
589 self.command_parser = self.create_parser_command(command)
590 (command_options, command_args) = self.command_parser.parse_args(args[1:])
591 if command_options.help:
594 self.command_options = command_options
598 self.logger.debug("Command={}".format(self.command))
601 retcod = self.dispatch(command, command_options, command_args)
605 self.logger.log_exc ("sfi command {} failed".format(command))
610 def read_config(self):
611 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
612 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
614 if Config.is_ini(config_file):
615 config = Config (config_file)
617 # try upgrading from shell config format
618 fp, fn = mkstemp(suffix='sfi_config', text=True)
620 # we need to preload the sections we want parsed
621 # from the shell config
622 config.add_section('sfi')
623 # sface users should be able to use this same file to configure their stuff
624 config.add_section('sface')
625 # manifold users should be able to specify the details
626 # of their backend server here for 'sfi myslice'
627 config.add_section('myslice')
628 config.load(config_file)
630 shutil.move(config_file, shell_config_file)
632 config.save(config_file)
635 self.logger.critical("Failed to read configuration file {}".format(config_file))
636 self.logger.info("Make sure to remove the export clauses and to add quotes")
637 if self.options.verbose==0:
638 self.logger.info("Re-run with -v for more details")
640 self.logger.log_exc("Could not read config file {}".format(config_file))
643 self.config_instance=config
646 if (self.options.sm is not None):
647 self.sm_url = self.options.sm
648 elif hasattr(config, "SFI_SM"):
649 self.sm_url = config.SFI_SM
651 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in {}".format(config_file))
655 if (self.options.registry is not None):
656 self.reg_url = self.options.registry
657 elif hasattr(config, "SFI_REGISTRY"):
658 self.reg_url = config.SFI_REGISTRY
660 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in {}".format(config_file))
664 if (self.options.user is not None):
665 self.user = self.options.user
666 elif hasattr(config, "SFI_USER"):
667 self.user = config.SFI_USER
669 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in {}".format(config_file))
673 if (self.options.auth is not None):
674 self.authority = self.options.auth
675 elif hasattr(config, "SFI_AUTH"):
676 self.authority = config.SFI_AUTH
678 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in {}".format(config_file))
681 self.config_file=config_file
686 # Get various credential and spec files
688 # Establishes limiting conventions
689 # - conflates MAs and SAs
690 # - assumes last token in slice name is unique
692 # Bootstraps credentials
693 # - bootstrap user credential from self-signed certificate
694 # - bootstrap authority credential from user credential
695 # - bootstrap slice credential from user credential
698 # init self-signed cert, user credentials and gid
699 def bootstrap (self):
700 if self.options.verbose:
701 self.logger.info("Initializing SfaClientBootstrap with {}".format(self.reg_url))
702 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
704 # if -k is provided, use this to initialize private key
705 if self.options.user_private_key:
706 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
708 # trigger legacy compat code if needed
709 # the name has changed from just <leaf>.pkey to <hrn>.pkey
710 if not os.path.isfile(client_bootstrap.private_key_filename()):
711 self.logger.info ("private key not found, trying legacy name")
713 legacy_private_key = os.path.join (self.options.sfi_dir, "{}.pkey"
714 .format(Xrn.unescape(get_leaf(self.user))))
715 self.logger.debug("legacy_private_key={}"
716 .format(legacy_private_key))
717 client_bootstrap.init_private_key_if_missing (legacy_private_key)
718 self.logger.info("Copied private key from legacy location {}"
719 .format(legacy_private_key))
721 self.logger.log_exc("Can't find private key ")
725 client_bootstrap.bootstrap_my_gid()
726 # extract what's needed
727 self.private_key = client_bootstrap.private_key()
728 self.my_credential_string = client_bootstrap.my_credential_string ()
729 self.my_credential = {'geni_type': 'geni_sfa',
731 'geni_value': self.my_credential_string}
732 self.my_gid = client_bootstrap.my_gid ()
733 self.client_bootstrap = client_bootstrap
736 def my_authority_credential_string(self):
737 if not self.authority:
738 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
740 return self.client_bootstrap.authority_credential_string (self.authority)
742 def authority_credential_string(self, auth_hrn):
743 return self.client_bootstrap.authority_credential_string (auth_hrn)
745 def slice_credential_string(self, name):
746 return self.client_bootstrap.slice_credential_string (name)
748 def slice_credential(self, name):
749 return {'geni_type': 'geni_sfa',
751 'geni_value': self.slice_credential_string(name)}
753 # xxx should be supported by sfaclientbootstrap as well
754 def delegate_cred(self, object_cred, hrn, type='authority'):
755 # the gid and hrn of the object we are delegating
756 if isinstance(object_cred, str):
757 object_cred = Credential(string=object_cred)
758 object_gid = object_cred.get_gid_object()
759 object_hrn = object_gid.get_hrn()
761 if not object_cred.get_privileges().get_all_delegate():
762 self.logger.error("Object credential {} does not have delegate bit set"
766 # the delegating user's gid
767 caller_gidfile = self.my_gid()
769 # the gid of the user who will be delegated to
770 delegee_gid = self.client_bootstrap.gid(hrn,type)
771 delegee_hrn = delegee_gid.get_hrn()
772 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
773 return dcred.save_to_string(save_parents=True)
776 # Management of the servers
781 if not hasattr (self, 'registry_proxy'):
782 self.logger.info("Contacting Registry at: {}".format(self.reg_url))
783 self.registry_proxy \
784 = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
785 timeout=self.options.timeout, verbose=self.options.debug)
786 return self.registry_proxy
790 if not hasattr (self, 'sliceapi_proxy'):
791 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
792 if hasattr(self.command_options,'component') and self.command_options.component:
793 # resolve the hrn at the registry
794 node_hrn = self.command_options.component
795 records = self.registry().Resolve(node_hrn, self.my_credential_string)
796 records = filter_records('node', records)
798 self.logger.warning("No such component:{}".format(opts.component))
800 cm_url = "http://{}:{}/".format(record['hostname'], CM_PORT)
801 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
803 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
804 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
805 self.sm_url = 'http://' + self.sm_url
806 self.logger.info("Contacting Slice Manager at: {}".format(self.sm_url))
807 self.sliceapi_proxy \
808 = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
809 timeout=self.options.timeout, verbose=self.options.debug)
810 return self.sliceapi_proxy
812 def get_cached_server_version(self, server):
813 # check local cache first
816 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
817 cache_key = server.url + "-version"
819 cache = Cache(cache_file)
822 self.logger.info("Local cache not found at: {}".format(cache_file))
825 version = cache.get(cache_key)
828 result = server.GetVersion()
829 version= ReturnValue.get_value(result)
830 # cache version for 20 minutes
831 cache.add(cache_key, version, ttl= 60*20)
832 self.logger.info("Updating cache file {}".format(cache_file))
833 cache.save_to_file(cache_file)
837 ### resurrect this temporarily so we can support V1 aggregates for a while
838 def server_supports_options_arg(self, server):
840 Returns true if server support the optional call_id arg, false otherwise.
842 server_version = self.get_cached_server_version(server)
844 # xxx need to rewrite this
845 if int(server_version.get('geni_api')) >= 2:
849 def server_supports_call_id_arg(self, server):
850 server_version = self.get_cached_server_version(server)
852 if 'sfa' in server_version and 'code_tag' in server_version:
853 code_tag = server_version['code_tag']
854 code_tag_parts = code_tag.split("-")
855 version_parts = code_tag_parts[0].split(".")
856 major, minor = version_parts[0], version_parts[1]
857 rev = code_tag_parts[1]
858 if int(major) == 1 and minor == 0 and build >= 22:
862 ### ois = options if supported
863 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
864 def ois (self, server, option_dict):
865 if self.server_supports_options_arg (server):
867 elif self.server_supports_call_id_arg (server):
868 return [ unique_call_id () ]
872 ### cis = call_id if supported - like ois
873 def cis (self, server):
874 if self.server_supports_call_id_arg (server):
875 return [ unique_call_id ]
879 ######################################## miscell utilities
880 def get_rspec_file(self, rspec):
881 if (os.path.isabs(rspec)):
884 file = os.path.join(self.options.sfi_dir, rspec)
885 if (os.path.isfile(file)):
888 self.logger.critical("No such rspec file {}".format(rspec))
891 def get_record_file(self, record):
892 if (os.path.isabs(record)):
895 file = os.path.join(self.options.sfi_dir, record)
896 if (os.path.isfile(file)):
899 self.logger.critical("No such registry record file {}".format(record))
903 # helper function to analyze raw output
904 # for main : return 0 if everything is fine, something else otherwise (mostly 1 for now)
905 def success (self, raw):
906 return_value=ReturnValue (raw)
907 output=ReturnValue.get_output(return_value)
908 # means everything is fine
911 # something went wrong
912 print 'ERROR:',output
915 #==========================================================================
916 # Following functions implement the commands
918 # Registry-related commands
919 #==========================================================================
921 @declare_command("","")
922 def config (self, options, args):
923 "Display contents of current config"
924 print "# From configuration file {}".format(self.config_file)
925 flags=[ ('sfi', [ ('registry','reg_url'),
926 ('auth','authority'),
932 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
934 for (section, tuples) in flags:
935 print "[{}]".format(section)
937 for (external_name, internal_name) in tuples:
938 print "{:-20} = {}".format(external_name, getattr(self, internal_name))
941 varname = "{}_{}".format(section.upper(), name.upper())
942 value = getattr(self.config_instance,varname)
943 print "{:-20} = {}".format(name, value)
944 # xxx should analyze result
947 @declare_command("","")
948 def version(self, options, args):
950 display an SFA server version (GetVersion)
951 or version information about sfi itself
953 if options.version_local:
954 version=version_core()
956 if options.registry_interface:
957 server=self.registry()
959 server = self.sliceapi()
960 result = server.GetVersion()
961 version = ReturnValue.get_value(result)
963 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
965 pprinter = PrettyPrinter(indent=4)
966 pprinter.pprint(version)
967 # xxx should analyze result
970 @declare_command("authority","")
971 def list(self, options, args):
973 list entries in named authority registry (List)
980 if options.recursive:
981 opts['recursive'] = options.recursive
983 if options.show_credential:
984 show_credentials(self.my_credential_string)
986 list = self.registry().List(hrn, self.my_credential_string, options)
988 raise Exception, "Not enough parameters for the 'list' command"
990 # filter on person, slice, site, node, etc.
991 # This really should be in the self.filter_records funct def comment...
992 list = filter_records(options.type, list)
993 terminal_render (list, options)
995 save_records_to_file(options.file, list, options.fileformat)
996 # xxx should analyze result
999 @declare_command("name","")
1000 def show(self, options, args):
1002 show details about named registry record (Resolve)
1008 # explicitly require Resolve to run in details mode
1010 if not options.no_details: resolve_options['details']=True
1011 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, resolve_options)
1012 record_dicts = filter_records(options.type, record_dicts)
1013 if not record_dicts:
1014 self.logger.error("No record of type {}".format(options.type))
1016 # user has required to focus on some keys
1018 def project (record):
1020 for key in options.keys:
1021 try: projected[key]=record[key]
1024 record_dicts = [ project (record) for record in record_dicts ]
1025 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
1026 for record in records:
1027 if (options.format == "text"): record.dump(sort=True)
1028 else: print record.save_as_xml()
1030 save_records_to_file(options.file, record_dicts, options.fileformat)
1031 # xxx should analyze result
1034 # this historically was named 'add', it is now 'register' with an alias for legacy
1035 @declare_command("[xml-filename]","",['add'])
1036 def register(self, options, args):
1037 """create new record in registry (Register)
1038 from command line options (recommended)
1039 old-school method involving an xml file still supported"""
1041 auth_cred = self.my_authority_credential_string()
1042 if options.show_credential:
1043 show_credentials(auth_cred)
1050 record_filepath = args[0]
1051 rec_file = self.get_record_file(record_filepath)
1052 record_dict.update(load_record_from_file(rec_file).todict())
1054 print "Cannot load record file {}".format(record_filepath)
1057 record_dict.update(load_record_from_opts(options).todict())
1058 # we should have a type by now
1059 if 'type' not in record_dict :
1062 # this is still planetlab dependent.. as plc will whine without that
1063 # also, it's only for adding
1064 if record_dict['type'] == 'user':
1065 if not 'first_name' in record_dict:
1066 record_dict['first_name'] = record_dict['hrn']
1067 if 'last_name' not in record_dict:
1068 record_dict['last_name'] = record_dict['hrn']
1069 register = self.registry().Register(record_dict, auth_cred)
1070 # xxx looks like the result here is not ReturnValue-compatible
1071 #return self.success (register)
1072 # xxx should analyze result
1075 @declare_command("[xml-filename]","")
1076 def update(self, options, args):
1077 """update record into registry (Update)
1078 from command line options (recommended)
1079 old-school method involving an xml file still supported"""
1082 record_filepath = args[0]
1083 rec_file = self.get_record_file(record_filepath)
1084 record_dict.update(load_record_from_file(rec_file).todict())
1086 record_dict.update(load_record_from_opts(options).todict())
1087 # at the very least we need 'type' here
1088 if 'type' not in record_dict or record_dict['type'] is None:
1092 # don't translate into an object, as this would possibly distort
1093 # user-provided data; e.g. add an 'email' field to Users
1094 if record_dict['type'] in ['user']:
1095 if record_dict['hrn'] == self.user:
1096 cred = self.my_credential_string
1098 cred = self.my_authority_credential_string()
1099 elif record_dict['type'] in ['slice']:
1101 cred = self.slice_credential_string(record_dict['hrn'])
1102 except ServerException, e:
1103 # XXX smbaker -- once we have better error return codes, update this
1104 # to do something better than a string compare
1105 if "Permission error" in e.args[0]:
1106 cred = self.my_authority_credential_string()
1109 elif record_dict['type'] in ['authority']:
1110 cred = self.my_authority_credential_string()
1111 elif record_dict['type'] in ['node']:
1112 cred = self.my_authority_credential_string()
1114 raise Exception("unknown record type {}".format(record_dict['type']))
1115 if options.show_credential:
1116 show_credentials(cred)
1117 update = self.registry().Update(record_dict, cred)
1118 # xxx looks like the result here is not ReturnValue-compatible
1119 #return self.success(update)
1120 # xxx should analyze result
1123 @declare_command("hrn","")
1124 def remove(self, options, args):
1125 "remove registry record by name (Remove)"
1126 auth_cred = self.my_authority_credential_string()
1134 if options.show_credential:
1135 show_credentials(auth_cred)
1136 remove = self.registry().Remove(hrn, auth_cred, type)
1137 # xxx looks like the result here is not ReturnValue-compatible
1138 #return self.success (remove)
1139 # xxx should analyze result
1142 # ==================================================================
1143 # Slice-related commands
1144 # ==================================================================
1146 # show rspec for named slice
1147 @declare_command("","",['discover'])
1148 def resources(self, options, args):
1150 discover available resources (ListResources)
1152 server = self.sliceapi()
1155 creds = [self.my_credential]
1156 if options.delegate:
1157 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1158 if options.show_credential:
1159 show_credentials(creds)
1161 # no need to check if server accepts the options argument since the options has
1162 # been a required argument since v1 API
1164 # always send call_id to v2 servers
1165 api_options ['call_id'] = unique_call_id()
1166 # ask for cached value if available
1167 api_options ['cached'] = True
1169 api_options['info'] = options.info
1170 if options.list_leases:
1171 api_options['list_leases'] = options.list_leases
1173 if options.current == True:
1174 api_options['cached'] = False
1176 api_options['cached'] = True
1177 if options.rspec_version:
1178 version_manager = VersionManager()
1179 server_version = self.get_cached_server_version(server)
1180 if 'sfa' in server_version:
1181 # just request the version the client wants
1182 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1184 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1186 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1187 list_resources = server.ListResources (creds, api_options)
1188 value = ReturnValue.get_value(list_resources)
1189 if self.options.raw:
1190 save_raw_to_file(list_resources, self.options.raw, self.options.rawformat, self.options.rawbanner)
1191 if options.file is not None:
1192 save_rspec_to_file(value, options.file)
1193 if (self.options.raw is None) and (options.file is None):
1194 display_rspec(value, options.format)
1195 return self.success(list_resources)
1197 @declare_command("slice_hrn","")
1198 def describe(self, options, args):
1200 shows currently allocated/provisioned resources
1201 of the named slice or set of slivers (Describe)
1203 server = self.sliceapi()
1206 creds = [self.slice_credential(args[0])]
1207 if options.delegate:
1208 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1209 if options.show_credential:
1210 show_credentials(creds)
1212 api_options = {'call_id': unique_call_id(),
1214 'info': options.info,
1215 'list_leases': options.list_leases,
1216 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1219 api_options['info'] = options.info
1221 if options.rspec_version:
1222 version_manager = VersionManager()
1223 server_version = self.get_cached_server_version(server)
1224 if 'sfa' in server_version:
1225 # just request the version the client wants
1226 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1228 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1229 urn = Xrn(args[0], type='slice').get_urn()
1230 remove_none_fields(api_options)
1231 describe = server.Describe([urn], creds, api_options)
1232 value = ReturnValue.get_value(describe)
1233 if self.options.raw:
1234 save_raw_to_file(describe, self.options.raw, self.options.rawformat, self.options.rawbanner)
1235 if options.file is not None:
1236 save_rspec_to_file(value['geni_rspec'], options.file)
1237 if (self.options.raw is None) and (options.file is None):
1238 display_rspec(value['geni_rspec'], options.format)
1239 return self.success (describe)
1241 @declare_command("slice_hrn [<sliver_urn>...]","")
1242 def delete(self, options, args):
1244 de-allocate and de-provision all or named slivers of the named slice (Delete)
1246 server = self.sliceapi()
1250 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1253 # we have sliver urns
1254 sliver_urns = args[1:]
1256 # we provision all the slivers of the slice
1257 sliver_urns = [slice_urn]
1260 slice_cred = self.slice_credential(slice_hrn)
1261 creds = [slice_cred]
1263 # options and call_id when supported
1265 api_options ['call_id'] = unique_call_id()
1266 if options.show_credential:
1267 show_credentials(creds)
1268 delete = server.Delete(sliver_urns, creds, *self.ois(server, api_options ) )
1269 value = ReturnValue.get_value(delete)
1270 if self.options.raw:
1271 save_raw_to_file(delete, self.options.raw, self.options.rawformat, self.options.rawbanner)
1274 return self.success (delete)
1276 @declare_command("slice_hrn rspec","")
1277 def allocate(self, options, args):
1279 allocate resources to the named slice (Allocate)
1281 server = self.sliceapi()
1282 server_version = self.get_cached_server_version(server)
1287 rspec_file = self.get_rspec_file(args[1])
1289 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1292 creds = [self.slice_credential(slice_hrn)]
1294 delegated_cred = None
1295 if server_version.get('interface') == 'slicemgr':
1296 # delegate our cred to the slice manager
1297 # do not delegate cred to slicemgr...not working at the moment
1299 #if server_version.get('hrn'):
1300 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1301 #elif server_version.get('urn'):
1302 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1304 if options.show_credential:
1305 show_credentials(creds)
1309 api_options ['call_id'] = unique_call_id()
1313 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1314 remove_none_fields(slice_records[0])
1315 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1316 slice_record = slice_records[0]
1317 user_hrns = slice_record['reg-researchers']
1318 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1319 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1320 sfa_users = sfa_users_arg(user_records, slice_record)
1321 geni_users = pg_users_arg(user_records)
1323 api_options['sfa_users'] = sfa_users
1324 api_options['geni_users'] = geni_users
1326 with open(rspec_file) as rspec:
1327 rspec_xml = rspec.read()
1328 allocate = server.Allocate(slice_urn, creds, rspec_xml, api_options)
1329 value = ReturnValue.get_value(allocate)
1330 if self.options.raw:
1331 save_raw_to_file(allocate, self.options.raw, self.options.rawformat, self.options.rawbanner)
1332 if options.file is not None:
1333 save_rspec_to_file (value['geni_rspec'], options.file)
1334 if (self.options.raw is None) and (options.file is None):
1336 return self.success(allocate)
1338 @declare_command("slice_hrn [<sliver_urn>...]","")
1339 def provision(self, options, args):
1341 provision all or named already allocated slivers of the named slice (Provision)
1343 server = self.sliceapi()
1344 server_version = self.get_cached_server_version(server)
1346 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1348 # we have sliver urns
1349 sliver_urns = args[1:]
1351 # we provision all the slivers of the slice
1352 sliver_urns = [slice_urn]
1355 creds = [self.slice_credential(slice_hrn)]
1356 delegated_cred = None
1357 if server_version.get('interface') == 'slicemgr':
1358 # delegate our cred to the slice manager
1359 # do not delegate cred to slicemgr...not working at the moment
1361 #if server_version.get('hrn'):
1362 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1363 #elif server_version.get('urn'):
1364 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1366 if options.show_credential:
1367 show_credentials(creds)
1370 api_options ['call_id'] = unique_call_id()
1372 # set the requtested rspec version
1373 version_manager = VersionManager()
1374 rspec_version = version_manager._get_version('geni', '3').to_dict()
1375 api_options['geni_rspec_version'] = rspec_version
1378 # need to pass along user keys to the aggregate.
1380 # { urn: urn:publicid:IDN+emulab.net+user+alice
1381 # keys: [<ssh key A>, <ssh key B>]
1384 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1385 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1386 slice_record = slice_records[0]
1387 user_hrns = slice_record['reg-researchers']
1388 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1389 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1390 users = pg_users_arg(user_records)
1392 api_options['geni_users'] = users
1393 provision = server.Provision(sliver_urns, creds, api_options)
1394 value = ReturnValue.get_value(provision)
1395 if self.options.raw:
1396 save_raw_to_file(provision, self.options.raw, self.options.rawformat, self.options.rawbanner)
1397 if options.file is not None:
1398 save_rspec_to_file (value['geni_rspec'], options.file)
1399 if (self.options.raw is None) and (options.file is None):
1401 return self.success(provision)
1403 @declare_command("slice_hrn","")
1404 def status(self, options, args):
1406 retrieve the status of the slivers belonging to the named slice (Status)
1408 server = self.sliceapi()
1412 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1415 slice_cred = self.slice_credential(slice_hrn)
1416 creds = [slice_cred]
1418 # options and call_id when supported
1420 api_options['call_id']=unique_call_id()
1421 if options.show_credential:
1422 show_credentials(creds)
1423 status = server.Status([slice_urn], creds, *self.ois(server,api_options))
1424 value = ReturnValue.get_value(status)
1425 if self.options.raw:
1426 save_raw_to_file(status, self.options.raw, self.options.rawformat, self.options.rawbanner)
1429 return self.success (status)
1431 @declare_command("slice_hrn [<sliver_urn>...] action","")
1432 def action(self, options, args):
1434 Perform the named operational action on all or named slivers of the named slice
1436 server = self.sliceapi()
1440 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1442 # we have sliver urns
1443 sliver_urns = args[1:-1]
1445 # we provision all the slivers of the slice
1446 sliver_urns = [slice_urn]
1449 slice_cred = self.slice_credential(args[0])
1450 creds = [slice_cred]
1451 if options.delegate:
1452 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1453 creds.append(delegated_cred)
1455 perform_action = server.PerformOperationalAction(sliver_urns, creds, action , api_options)
1456 value = ReturnValue.get_value(perform_action)
1457 if self.options.raw:
1458 save_raw_to_file(perform_action, self.options.raw, self.options.rawformat, self.options.rawbanner)
1461 return self.success (perform_action)
1463 @declare_command("slice_hrn [<sliver_urn>...] time",
1464 "\n".join(["sfi renew onelab.ple.heartbeat 2015-04-31",
1465 "sfi renew onelab.ple.heartbeat 2015-04-31T14:00:00Z",
1466 "sfi renew onelab.ple.heartbeat +5d",
1467 "sfi renew onelab.ple.heartbeat +3w",
1468 "sfi renew onelab.ple.heartbeat +2m",]))
1469 def renew(self, options, args):
1473 server = self.sliceapi()
1478 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1481 # we have sliver urns
1482 sliver_urns = args[1:-1]
1484 # we provision all the slivers of the slice
1485 sliver_urns = [slice_urn]
1486 input_time = args[-1]
1488 # time: don't try to be smart on the time format, server-side will
1490 slice_cred = self.slice_credential(args[0])
1491 creds = [slice_cred]
1492 # options and call_id when supported
1494 api_options['call_id']=unique_call_id()
1496 api_options['geni_extend_alap']=True
1497 if options.show_credential:
1498 show_credentials(creds)
1499 renew = server.Renew(sliver_urns, creds, input_time, *self.ois(server,api_options))
1500 value = ReturnValue.get_value(renew)
1501 if self.options.raw:
1502 save_raw_to_file(renew, self.options.raw, self.options.rawformat, self.options.rawbanner)
1505 return self.success(renew)
1507 @declare_command("slice_hrn","")
1508 def shutdown(self, options, args):
1510 shutdown named slice (Shutdown)
1512 server = self.sliceapi()
1515 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1517 slice_cred = self.slice_credential(slice_hrn)
1518 creds = [slice_cred]
1519 shutdown = server.Shutdown(slice_urn, creds)
1520 value = ReturnValue.get_value(shutdown)
1521 if self.options.raw:
1522 save_raw_to_file(shutdown, self.options.raw, self.options.rawformat, self.options.rawbanner)
1525 return self.success (shutdown)
1527 @declare_command("[name]","")
1528 def gid(self, options, args):
1530 Create a GID (CreateGid)
1535 target_hrn = args[0]
1536 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1537 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1539 filename = options.file
1541 filename = os.sep.join([self.options.sfi_dir, '{}.gid'.format(target_hrn)])
1542 self.logger.info("writing {} gid to {}".format(target_hrn, filename))
1543 GID(string=gid).save_to_file(filename)
1544 # xxx should analyze result
1547 ####################
1548 @declare_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1550 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1551 the set of credentials in the scope for this call would be
1552 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1554 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1556 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1557 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1558 because of the two -s options
1561 def delegate (self, options, args):
1563 (locally) create delegate credential for use by given hrn
1564 make sure to check for 'sfi myslice' instead if you plan
1571 # support for several delegations in the same call
1572 # so first we gather the things to do
1574 for slice_hrn in options.delegate_slices:
1575 message = "{}.slice".format(slice_hrn)
1576 original = self.slice_credential_string(slice_hrn)
1577 tuples.append ( (message, original,) )
1578 if options.delegate_pi:
1579 my_authority=self.authority
1580 message = "{}.pi".format(my_authority)
1581 original = self.my_authority_credential_string()
1582 tuples.append ( (message, original,) )
1583 for auth_hrn in options.delegate_auths:
1584 message = "{}.auth".format(auth_hrn)
1585 original = self.authority_credential_string(auth_hrn)
1586 tuples.append ( (message, original, ) )
1587 # if nothing was specified at all at this point, let's assume -u
1588 if not tuples: options.delegate_user=True
1590 if options.delegate_user:
1591 message = "{}.user".format(self.user)
1592 original = self.my_credential_string
1593 tuples.append ( (message, original, ) )
1595 # default type for beneficial is user unless -A
1596 if options.delegate_to_authority: to_type='authority'
1597 else: to_type='user'
1599 # let's now handle all this
1600 # it's all in the filenaming scheme
1601 for (message,original) in tuples:
1602 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1603 delegated_credential = Credential (string=delegated_string)
1604 filename = os.path.join(self.options.sfi_dir,
1605 "{}_for_{}.{}.cred".format(message, to_hrn, to_type))
1606 delegated_credential.save_to_file(filename, save_parents=True)
1607 self.logger.info("delegated credential for {} to {} and wrote to {}"
1608 .format(message, to_hrn, filename))
1610 ####################
1611 @declare_command("","""$ less +/myslice sfi_config
1613 backend = http://manifold.pl.sophia.inria.fr:7080
1614 # the HRN that myslice uses, so that we are delegating to
1615 delegate = ple.upmc.slicebrowser
1616 # platform - this is a myslice concept
1618 # username - as of this writing (May 2013) a simple login name
1622 will first collect the slices that you are part of, then make sure
1623 all your credentials are up-to-date (read: refresh expired ones)
1624 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1625 and upload them all on myslice backend, using 'platform' and 'user'.
1626 A password will be prompted for the upload part.
1628 $ sfi -v myslice -- or sfi -vv myslice
1629 same but with more and more verbosity
1631 $ sfi m -b http://mymanifold.foo.com:7080/
1632 is synonym to sfi myslice as no other command starts with an 'm'
1633 and uses a custom backend for this one call
1636 def myslice (self, options, args):
1638 """ This helper is for refreshing your credentials at myslice; it will
1639 * compute all the slices that you currently have credentials on
1640 * refresh all your credentials (you as a user and pi, your slices)
1641 * upload them to the manifold backend server
1642 for last phase, sfi_config is read to look for the [myslice] section,
1643 and namely the 'backend', 'delegate' and 'user' settings"""
1649 # enable info by default
1650 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1651 ### the rough sketch goes like this
1652 # (0) produce a p12 file
1653 self.client_bootstrap.my_pkcs12()
1655 # (a) rain check for sufficient config in sfi_config
1657 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1658 for key in myslice_keys:
1660 # oct 2013 - I'm finding myself juggling with config files
1661 # so a couple of command-line options can now override config
1662 if hasattr(options,key) and getattr(options,key) is not None:
1663 value=getattr(options,key)
1665 full_key="MYSLICE_" + key.upper()
1666 value=getattr(self.config_instance,full_key,None)
1668 myslice_dict[key]=value
1670 print "Unsufficient config, missing key {} in [myslice] section of sfi_config"\
1672 if len(myslice_dict) != len(myslice_keys):
1675 # (b) figure whether we are PI for the authority where we belong
1676 self.logger.info("Resolving our own id {}".format(self.user))
1677 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1678 if len(my_records) != 1:
1679 print "Cannot Resolve {} -- exiting".format(self.user)
1681 my_record = my_records[0]
1682 my_auths_all = my_record['reg-pi-authorities']
1683 self.logger.info("Found {} authorities that we are PI for".format(len(my_auths_all)))
1684 self.logger.debug("They are {}".format(my_auths_all))
1686 my_auths = my_auths_all
1687 if options.delegate_auths:
1688 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1689 self.logger.debug("Restricted to user-provided auths {}".format(my_auths))
1691 # (c) get the set of slices that we are in
1692 my_slices_all=my_record['reg-slices']
1693 self.logger.info("Found {} slices that we are member of".format(len(my_slices_all)))
1694 self.logger.debug("They are: {}".format(my_slices_all))
1696 my_slices = my_slices_all
1697 # if user provided slices, deal only with these - if they are found
1698 if options.delegate_slices:
1699 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1700 self.logger.debug("Restricted to user-provided slices: {}".format(my_slices))
1702 # (d) make sure we have *valid* credentials for all these
1704 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1705 for auth_hrn in my_auths:
1706 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1707 for slice_hrn in my_slices:
1708 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1710 # (e) check for the delegated version of these
1711 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1712 # switch to myslice using an authority instead of a user
1713 delegatee_type='user'
1714 delegatee_hrn=myslice_dict['delegate']
1715 hrn_delegated_credentials = []
1716 for (hrn, htype, credential) in hrn_credentials:
1717 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1718 # save these so user can monitor what she's uploaded
1719 filename = os.path.join ( self.options.sfi_dir,
1720 "{}.{}_for_{}.{}.cred"\
1721 .format(hrn, htype, delegatee_hrn, delegatee_type))
1722 with file(filename,'w') as f:
1723 f.write(delegated_credential)
1724 self.logger.debug("(Over)wrote {}".format(filename))
1725 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1727 # (f) and finally upload them to manifold server
1728 # xxx todo add an option so the password can be set on the command line
1729 # (but *NOT* in the config file) so other apps can leverage this
1730 self.logger.info("Uploading on backend at {}".format(myslice_dict['backend']))
1731 uploader = ManifoldUploader (logger=self.logger,
1732 url=myslice_dict['backend'],
1733 platform=myslice_dict['platform'],
1734 username=myslice_dict['username'],
1735 password=options.password)
1736 uploader.prompt_all()
1737 (count_all,count_success)=(0,0)
1738 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1740 inspect=Credential(string=delegated_credential)
1741 expire_datetime=inspect.get_expiration()
1742 message="{} ({}) [exp:{}]".format(hrn, htype, expire_datetime)
1743 if uploader.upload(delegated_credential,message=message):
1746 self.logger.info("Successfully uploaded {}/{} credentials"
1747 .format(count_success, count_all))
1749 # at first I thought we would want to save these,
1750 # like 'sfi delegate does' but on second thought
1751 # it is probably not helpful as people would not
1752 # need to run 'sfi delegate' at all anymore
1753 if count_success != count_all: sys.exit(1)
1754 # xxx should analyze result
1757 @declare_command("cred","")
1758 def trusted(self, options, args):
1760 return the trusted certs at this interface (get_trusted_certs)
1762 if options.registry_interface:
1763 server=self.registry()
1765 server = self.sliceapi()
1766 cred = self.my_authority_credential_string()
1767 trusted_certs = server.get_trusted_certs(cred)
1768 if not options.registry_interface:
1769 trusted_certs = ReturnValue.get_value(trusted_certs)
1771 for trusted_cert in trusted_certs:
1772 print "\n===========================================================\n"
1773 gid = GID(string=trusted_cert)
1775 cert = Certificate(string=trusted_cert)
1776 self.logger.debug('Sfi.trusted -> {}'.format(cert.get_subject()))
1777 print "Certificate:\n{}\n\n".format(trusted_cert)
1778 # xxx should analyze result