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'):
196 elif type.startswith('ag'):
198 elif type.startswith('al'):
201 print 'unknown type {} - should start with one of au|us|sl|no|ag|al'.format(type)
204 def load_record_from_opts(options):
206 if hasattr(options, 'xrn') and options.xrn:
207 if hasattr(options, 'type') and options.type:
208 xrn = Xrn(options.xrn, options.type)
210 xrn = Xrn(options.xrn)
211 record_dict['urn'] = xrn.get_urn()
212 record_dict['hrn'] = xrn.get_hrn()
213 record_dict['type'] = xrn.get_type()
214 if hasattr(options, 'key') and options.key:
216 pubkey = open(options.key, 'r').read()
219 if not check_ssh_key (pubkey):
220 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
221 record_dict['reg-keys'] = [pubkey]
222 if hasattr(options, 'slices') and options.slices:
223 record_dict['slices'] = options.slices
224 if hasattr(options, 'reg_researchers') and options.reg_researchers is not None:
225 record_dict['reg-researchers'] = options.reg_researchers
226 if hasattr(options, 'email') and options.email:
227 record_dict['email'] = options.email
228 # authorities can have a name for standalone deployment
229 if hasattr(options, 'name') and options.name:
230 record_dict['name'] = options.name
231 if hasattr(options, 'reg_pis') and options.reg_pis:
232 record_dict['reg-pis'] = options.reg_pis
234 # handle extra settings
235 record_dict.update(options.extras)
237 return Record(dict=record_dict)
239 def load_record_from_file(filename):
240 f=codecs.open(filename, encoding="utf-8", mode="r")
241 xml_string = f.read()
243 return Record(xml=xml_string)
247 def unique_call_id(): return uuid.uuid4().urn
249 ########## a simple model for maintaing 3 doc attributes per command (instead of just one)
250 # essentially for the methods that implement a subcommand like sfi list
251 # we need to keep track of
252 # (*) doc a few lines that tell what it does, still located in __doc__
253 # (*) args_string a simple one-liner that describes mandatory arguments
254 # (*) example well, one or several releant examples
256 # since __doc__ only accounts for one, we use this simple mechanism below
257 # however we keep doc in place for easier migration
259 from functools import wraps
261 # we use a list as well as a dict so we can keep track of the order
265 def declare_command (args_string, example,aliases=None):
267 name=getattr(m,'__name__')
268 doc=getattr(m,'__doc__',"-- missing doc --")
269 doc=doc.strip(" \t\n")
270 commands_list.append(name)
271 # last item is 'canonical' name, so we can know which commands are aliases
272 command_tuple=(doc, args_string, example,name)
273 commands_dict[name]=command_tuple
274 if aliases is not None:
275 for alias in aliases:
276 commands_list.append(alias)
277 commands_dict[alias]=command_tuple
279 def new_method (*args, **kwds): return m(*args, **kwds)
284 def remove_none_fields (record):
285 none_fields=[ k for (k,v) in record.items() if v is None ]
286 for k in none_fields: del record[k]
292 # dirty hack to make this class usable from the outside
293 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
296 def default_sfi_dir ():
297 if os.path.isfile("./sfi_config"):
300 return os.path.expanduser("~/.sfi/")
302 # dummy to meet Sfi's expectations for its 'options' field
303 # i.e. s/t we can do setattr on
307 def __init__ (self,options=None):
308 if options is None: options=Sfi.DummyOptions()
309 for opt in Sfi.required_options:
310 if not hasattr(options,opt): setattr(options,opt,None)
311 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
312 self.options = options
314 self.authority = None
315 self.logger = sfi_logger
316 self.logger.enable_console()
317 ### various auxiliary material that we keep at hand
319 # need to call this other than just 'config' as we have a command/method with that name
320 self.config_instance=None
321 self.config_file=None
322 self.client_bootstrap=None
324 ### suitable if no reasonable command has been provided
325 def print_commands_help (self, options):
326 verbose=getattr(options,'verbose')
327 format3="%10s %-35s %s"
331 print format3%("command", "cmd_args", "description")
335 self.create_parser_global().print_help()
336 # preserve order from the code
337 for command in commands_list:
339 (doc, args_string, example, canonical) = commands_dict[command]
341 print "Cannot find info on command %s - skipped"%command
345 if command==canonical:
346 doc = doc.replace("\n", "\n" + format3offset * ' ')
347 print format3 % (command,args_string,doc)
349 self.create_parser_command(command).print_help()
351 print format3 % (command,"<<alias for %s>>"%canonical,"")
353 ### now if a known command was found we can be more verbose on that one
354 def print_help (self):
355 print "==================== Generic sfi usage"
356 self.sfi_parser.print_help()
357 (doc, _, example, canonical) = commands_dict[self.command]
358 if canonical != self.command:
359 print "\n==================== NOTE: {} is an alias for genuine {}"\
360 .format(self.command, canonical)
361 self.command = canonical
362 print "\n==================== Purpose of {}".format(self.command)
364 print "\n==================== Specific usage for {}".format(self.command)
365 self.command_parser.print_help()
367 print "\n==================== {} example(s)".format(self.command)
370 def create_parser_global(self):
371 # Generate command line parser
372 parser = OptionParser(add_help_option=False,
373 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
374 description="Commands: {}".format(" ".join(commands_list)))
375 parser.add_option("-r", "--registry", dest="registry",
376 help="root registry", metavar="URL", default=None)
377 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
378 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
379 parser.add_option("-R", "--raw", dest="raw", default=None,
380 help="Save raw, unparsed server response to a file")
381 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
382 help="raw file format ([text]|pickled|json)", default="text",
383 choices=("text","pickled","json"))
384 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
385 help="text string to write before and after raw output")
386 parser.add_option("-d", "--dir", dest="sfi_dir",
387 help="config & working directory - default is %default",
388 metavar="PATH", default=Sfi.default_sfi_dir())
389 parser.add_option("-u", "--user", dest="user",
390 help="user name", metavar="HRN", default=None)
391 parser.add_option("-a", "--auth", dest="auth",
392 help="authority name", metavar="HRN", default=None)
393 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
394 help="verbose mode - cumulative")
395 parser.add_option("-D", "--debug",
396 action="store_true", dest="debug", default=False,
397 help="Debug (xml-rpc) protocol messages")
398 # would it make sense to use ~/.ssh/id_rsa as a default here ?
399 parser.add_option("-k", "--private-key",
400 action="store", dest="user_private_key", default=None,
401 help="point to the private key file to use if not yet installed in sfi_dir")
402 parser.add_option("-t", "--timeout", dest="timeout", default=None,
403 help="Amout of time to wait before timing out the request")
404 parser.add_option("-h", "--help",
405 action="store_true", dest="help", default=False,
406 help="one page summary on commands & exit")
407 parser.disable_interspersed_args()
412 def create_parser_command(self, command):
413 if command not in commands_dict:
414 msg="Invalid command\n"
416 msg += ','.join(commands_list)
417 self.logger.critical(msg)
420 # retrieve args_string
421 (_, args_string, __,canonical) = commands_dict[command]
423 parser = OptionParser(add_help_option=False,
424 usage="sfi [sfi_options] {} [cmd_options] {}"\
425 .format(command, args_string))
426 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
427 help="Summary of one command usage")
429 if canonical in ("config"):
430 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
431 help='how myslice config variables as well')
433 if canonical in ("version"):
434 parser.add_option("-l","--local",
435 action="store_true", dest="version_local", default=False,
436 help="display version of the local client")
438 if canonical in ("version", "trusted"):
439 parser.add_option("-R","--registry_interface",
440 action="store_true", dest="registry_interface", default=False,
441 help="target the registry interface instead of slice interface")
443 if canonical in ("register", "update"):
444 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
445 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type (2 first chars is enough)', default=None)
446 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
447 parser.add_option('-n', '--name', dest='name', default="", help="name (optional for authorities)")
448 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
450 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
451 default='', type="str", action='callback', callback=optparse_listvalue_callback)
452 parser.add_option('-r', '--researchers', dest='reg_researchers', metavar='<researchers>',
453 help='Set/replace slice researchers - use -r none to reset', default=None, type="str", action='callback',
454 callback=optparse_listvalue_callback)
455 parser.add_option('-p', '--pis', dest='reg_pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
456 default='', type="str", action='callback', callback=optparse_listvalue_callback)
457 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
458 action="callback", callback=optparse_dictvalue_callback, nargs=1,
459 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
461 # user specifies remote aggregate/sm/component
462 if canonical in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
463 "action", "shutdown", "renew", "status"):
464 parser.add_option("-d", "--delegate", dest="delegate", default=None,
466 help="Include a credential delegated to the user's root"+\
467 "authority in set of credentials for this call")
469 # show_credential option
470 if canonical in ("list","resources", "describe", "provision", "allocate", "register","update","remove","delete","status","renew"):
471 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
472 help="show credential(s) used in human-readable form")
473 if canonical in ("renew"):
474 parser.add_option("-l","--as-long-as-possible",dest='alap',action='store_true',default=False,
475 help="renew as long as possible")
476 # registy filter option
477 if canonical in ("list", "show", "remove"):
478 parser.add_option("-t", "--type", dest="type", metavar="<type>",
480 help="type filter - 2 first chars is enough ([all]|user|slice|authority|node|aggregate)")
481 if canonical in ("show"):
482 parser.add_option("-k","--key",dest="keys",action="append",default=[],
483 help="specify specific keys to be displayed from record")
484 parser.add_option("-n","--no-details",dest="no_details",action="store_true",default=False,
485 help="call Resolve without the 'details' option")
486 if canonical in ("resources", "describe"):
488 parser.add_option("-r", "--rspec-version", dest="rspec_version", default=DEFAULT_RSPEC_VERSION,
489 help="schema type and version of resulting RSpec (default:{})".format(DEFAULT_RSPEC_VERSION))
490 # disable/enable cached rspecs
491 parser.add_option("-c", "--current", dest="current", default=False,
493 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
495 parser.add_option("-f", "--format", dest="format", type="choice",
496 help="display format ([xml]|dns|ip)", default="xml",
497 choices=("xml", "dns", "ip"))
498 #panos: a new option to define the type of information about resources a user is interested in
499 parser.add_option("-i", "--info", dest="info",
500 help="optional component information", default=None)
501 # a new option to retrieve or not reservation-oriented RSpecs (leases)
502 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
503 help="Retrieve or not reservation-oriented RSpecs ([resources]|leases|all)",
504 choices=("all", "resources", "leases"), default="resources")
507 if canonical in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
508 parser.add_option("-o", "--output", dest="file",
509 help="output XML to file", metavar="FILE", default=None)
511 if canonical in ("show", "list"):
512 parser.add_option("-f", "--format", dest="format", type="choice",
513 help="display format ([text]|xml)", default="text",
514 choices=("text", "xml"))
516 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
517 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
518 choices=("xml", "xmllist", "hrnlist"))
519 if canonical == 'list':
520 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
521 help="list all child records", default=False)
522 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
523 help="gives details, like user keys", default=False)
524 if canonical in ("delegate"):
525 parser.add_option("-u", "--user",
526 action="store_true", dest="delegate_user", default=False,
527 help="delegate your own credentials; default if no other option is provided")
528 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
529 metavar="slice_hrn", help="delegate cred. for slice HRN")
530 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
531 metavar='auth_hrn', help="delegate cred for auth HRN")
532 # this primarily is a shorthand for -A my_hrn^
533 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
534 help="delegate your PI credentials, so s.t. like -A your_hrn^")
535 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
536 help="""by default the mandatory argument is expected to be a user,
537 use this if you mean an authority instead""")
539 if canonical in ("myslice"):
540 parser.add_option("-p","--password",dest='password',action='store',default=None,
541 help="specify mainfold password on the command line")
542 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
543 metavar="slice_hrn", help="delegate cred. for slice HRN")
544 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
545 metavar='auth_hrn', help="delegate PI cred for auth HRN")
546 parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
547 parser.add_option('-b', '--backend', dest='backend', help="Override 'backend' from the config file")
553 # Main: parse arguments and dispatch to command
555 def dispatch(self, command, command_options, command_args):
556 (doc, args_string, example, canonical) = commands_dict[command]
557 method=getattr(self, canonical, None)
559 print "sfi: unknown command {}".format(command)
560 raise SystemExit("Unknown command {}".format(command))
561 for arg in command_args:
562 if 'help' in arg or arg == '-h':
565 return method(command_options, command_args)
568 self.sfi_parser = self.create_parser_global()
569 (options, args) = self.sfi_parser.parse_args()
571 self.print_commands_help(options)
573 self.options = options
575 self.logger.setLevelFromOptVerbose(self.options.verbose)
578 self.logger.critical("No command given. Use -h for help.")
579 self.print_commands_help(options)
582 # complete / find unique match with command set
583 command_candidates = Candidates (commands_list)
585 command = command_candidates.only_match(input)
587 self.print_commands_help(options)
589 # second pass options parsing
591 self.command_parser = self.create_parser_command(command)
592 (command_options, command_args) = self.command_parser.parse_args(args[1:])
593 if command_options.help:
596 self.command_options = command_options
598 # allow incoming types on 2 characters only
599 if hasattr(command_options, 'type'):
600 command_options.type = normalize_type(command_options.type)
601 if not command_options.type:
606 self.logger.debug("Command={}".format(self.command))
609 retcod = self.dispatch(command, command_options, command_args)
613 self.logger.log_exc ("sfi command {} failed".format(command))
618 def read_config(self):
619 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
620 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
622 if Config.is_ini(config_file):
623 config = Config (config_file)
625 # try upgrading from shell config format
626 fp, fn = mkstemp(suffix='sfi_config', text=True)
628 # we need to preload the sections we want parsed
629 # from the shell config
630 config.add_section('sfi')
631 # sface users should be able to use this same file to configure their stuff
632 config.add_section('sface')
633 # manifold users should be able to specify the details
634 # of their backend server here for 'sfi myslice'
635 config.add_section('myslice')
636 config.load(config_file)
638 shutil.move(config_file, shell_config_file)
640 config.save(config_file)
643 self.logger.critical("Failed to read configuration file {}".format(config_file))
644 self.logger.info("Make sure to remove the export clauses and to add quotes")
645 if self.options.verbose==0:
646 self.logger.info("Re-run with -v for more details")
648 self.logger.log_exc("Could not read config file {}".format(config_file))
651 self.config_instance=config
654 if (self.options.sm is not None):
655 self.sm_url = self.options.sm
656 elif hasattr(config, "SFI_SM"):
657 self.sm_url = config.SFI_SM
659 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in {}".format(config_file))
663 if (self.options.registry is not None):
664 self.reg_url = self.options.registry
665 elif hasattr(config, "SFI_REGISTRY"):
666 self.reg_url = config.SFI_REGISTRY
668 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in {}".format(config_file))
672 if (self.options.user is not None):
673 self.user = self.options.user
674 elif hasattr(config, "SFI_USER"):
675 self.user = config.SFI_USER
677 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in {}".format(config_file))
681 if (self.options.auth is not None):
682 self.authority = self.options.auth
683 elif hasattr(config, "SFI_AUTH"):
684 self.authority = config.SFI_AUTH
686 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in {}".format(config_file))
689 self.config_file=config_file
694 # Get various credential and spec files
696 # Establishes limiting conventions
697 # - conflates MAs and SAs
698 # - assumes last token in slice name is unique
700 # Bootstraps credentials
701 # - bootstrap user credential from self-signed certificate
702 # - bootstrap authority credential from user credential
703 # - bootstrap slice credential from user credential
706 # init self-signed cert, user credentials and gid
707 def bootstrap (self):
708 if self.options.verbose:
709 self.logger.info("Initializing SfaClientBootstrap with {}".format(self.reg_url))
710 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
712 # if -k is provided, use this to initialize private key
713 if self.options.user_private_key:
714 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
716 # trigger legacy compat code if needed
717 # the name has changed from just <leaf>.pkey to <hrn>.pkey
718 if not os.path.isfile(client_bootstrap.private_key_filename()):
719 self.logger.info ("private key not found, trying legacy name")
721 legacy_private_key = os.path.join (self.options.sfi_dir, "{}.pkey"
722 .format(Xrn.unescape(get_leaf(self.user))))
723 self.logger.debug("legacy_private_key={}"
724 .format(legacy_private_key))
725 client_bootstrap.init_private_key_if_missing (legacy_private_key)
726 self.logger.info("Copied private key from legacy location {}"
727 .format(legacy_private_key))
729 self.logger.log_exc("Can't find private key ")
733 client_bootstrap.bootstrap_my_gid()
734 # extract what's needed
735 self.private_key = client_bootstrap.private_key()
736 self.my_credential_string = client_bootstrap.my_credential_string ()
737 self.my_credential = {'geni_type': 'geni_sfa',
739 'geni_value': self.my_credential_string}
740 self.my_gid = client_bootstrap.my_gid ()
741 self.client_bootstrap = client_bootstrap
744 def my_authority_credential_string(self):
745 if not self.authority:
746 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
748 return self.client_bootstrap.authority_credential_string (self.authority)
750 def authority_credential_string(self, auth_hrn):
751 return self.client_bootstrap.authority_credential_string (auth_hrn)
753 def slice_credential_string(self, name):
754 return self.client_bootstrap.slice_credential_string (name)
756 def slice_credential(self, name):
757 return {'geni_type': 'geni_sfa',
759 'geni_value': self.slice_credential_string(name)}
761 # xxx should be supported by sfaclientbootstrap as well
762 def delegate_cred(self, object_cred, hrn, type='authority'):
763 # the gid and hrn of the object we are delegating
764 if isinstance(object_cred, str):
765 object_cred = Credential(string=object_cred)
766 object_gid = object_cred.get_gid_object()
767 object_hrn = object_gid.get_hrn()
769 if not object_cred.get_privileges().get_all_delegate():
770 self.logger.error("Object credential {} does not have delegate bit set"
774 # the delegating user's gid
775 caller_gidfile = self.my_gid()
777 # the gid of the user who will be delegated to
778 delegee_gid = self.client_bootstrap.gid(hrn,type)
779 delegee_hrn = delegee_gid.get_hrn()
780 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
781 return dcred.save_to_string(save_parents=True)
784 # Management of the servers
789 if not hasattr (self, 'registry_proxy'):
790 self.logger.info("Contacting Registry at: {}".format(self.reg_url))
791 self.registry_proxy \
792 = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
793 timeout=self.options.timeout, verbose=self.options.debug)
794 return self.registry_proxy
798 if not hasattr (self, 'sliceapi_proxy'):
799 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
800 if hasattr(self.command_options,'component') and self.command_options.component:
801 # resolve the hrn at the registry
802 node_hrn = self.command_options.component
803 records = self.registry().Resolve(node_hrn, self.my_credential_string)
804 records = filter_records('node', records)
806 self.logger.warning("No such component:{}".format(opts.component))
808 cm_url = "http://{}:{}/".format(record['hostname'], CM_PORT)
809 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
811 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
812 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
813 self.sm_url = 'http://' + self.sm_url
814 self.logger.info("Contacting Slice Manager at: {}".format(self.sm_url))
815 self.sliceapi_proxy \
816 = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
817 timeout=self.options.timeout, verbose=self.options.debug)
818 return self.sliceapi_proxy
820 def get_cached_server_version(self, server):
821 # check local cache first
824 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
825 cache_key = server.url + "-version"
827 cache = Cache(cache_file)
830 self.logger.info("Local cache not found at: {}".format(cache_file))
833 version = cache.get(cache_key)
836 result = server.GetVersion()
837 version= ReturnValue.get_value(result)
838 # cache version for 20 minutes
839 cache.add(cache_key, version, ttl= 60*20)
840 self.logger.info("Updating cache file {}".format(cache_file))
841 cache.save_to_file(cache_file)
845 ### resurrect this temporarily so we can support V1 aggregates for a while
846 def server_supports_options_arg(self, server):
848 Returns true if server support the optional call_id arg, false otherwise.
850 server_version = self.get_cached_server_version(server)
852 # xxx need to rewrite this
853 if int(server_version.get('geni_api')) >= 2:
857 def server_supports_call_id_arg(self, server):
858 server_version = self.get_cached_server_version(server)
860 if 'sfa' in server_version and 'code_tag' in server_version:
861 code_tag = server_version['code_tag']
862 code_tag_parts = code_tag.split("-")
863 version_parts = code_tag_parts[0].split(".")
864 major, minor = version_parts[0], version_parts[1]
865 rev = code_tag_parts[1]
866 if int(major) == 1 and minor == 0 and build >= 22:
870 ### ois = options if supported
871 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
872 def ois (self, server, option_dict):
873 if self.server_supports_options_arg (server):
875 elif self.server_supports_call_id_arg (server):
876 return [ unique_call_id () ]
880 ### cis = call_id if supported - like ois
881 def cis (self, server):
882 if self.server_supports_call_id_arg (server):
883 return [ unique_call_id ]
887 ######################################## miscell utilities
888 def get_rspec_file(self, rspec):
889 if (os.path.isabs(rspec)):
892 file = os.path.join(self.options.sfi_dir, rspec)
893 if (os.path.isfile(file)):
896 self.logger.critical("No such rspec file {}".format(rspec))
899 def get_record_file(self, record):
900 if (os.path.isabs(record)):
903 file = os.path.join(self.options.sfi_dir, record)
904 if (os.path.isfile(file)):
907 self.logger.critical("No such registry record file {}".format(record))
911 # helper function to analyze raw output
912 # for main : return 0 if everything is fine, something else otherwise (mostly 1 for now)
913 def success (self, raw):
914 return_value=ReturnValue (raw)
915 output=ReturnValue.get_output(return_value)
916 # means everything is fine
919 # something went wrong
920 print 'ERROR:',output
923 #==========================================================================
924 # Following functions implement the commands
926 # Registry-related commands
927 #==========================================================================
929 @declare_command("","")
930 def config (self, options, args):
931 "Display contents of current config"
932 print "# From configuration file {}".format(self.config_file)
933 flags=[ ('sfi', [ ('registry','reg_url'),
934 ('auth','authority'),
940 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
942 for (section, tuples) in flags:
943 print "[{}]".format(section)
945 for (external_name, internal_name) in tuples:
946 print "{:-20} = {}".format(external_name, getattr(self, internal_name))
949 varname = "{}_{}".format(section.upper(), name.upper())
950 value = getattr(self.config_instance,varname)
951 print "{:-20} = {}".format(name, value)
952 # xxx should analyze result
955 @declare_command("","")
956 def version(self, options, args):
958 display an SFA server version (GetVersion)
959 or version information about sfi itself
961 if options.version_local:
962 version=version_core()
964 if options.registry_interface:
965 server=self.registry()
967 server = self.sliceapi()
968 result = server.GetVersion()
969 version = ReturnValue.get_value(result)
971 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
973 pprinter = PrettyPrinter(indent=4)
974 pprinter.pprint(version)
975 # xxx should analyze result
978 @declare_command("authority","")
979 def list(self, options, args):
981 list entries in named authority registry (List)
988 if options.recursive:
989 opts['recursive'] = options.recursive
991 if options.show_credential:
992 show_credentials(self.my_credential_string)
994 list = self.registry().List(hrn, self.my_credential_string, options)
996 raise Exception, "Not enough parameters for the 'list' command"
998 # filter on person, slice, site, node, etc.
999 # This really should be in the self.filter_records funct def comment...
1000 list = filter_records(options.type, list)
1001 terminal_render (list, options)
1003 save_records_to_file(options.file, list, options.fileformat)
1004 # xxx should analyze result
1007 @declare_command("name","")
1008 def show(self, options, args):
1010 show details about named registry record (Resolve)
1016 # explicitly require Resolve to run in details mode
1018 if not options.no_details: resolve_options['details']=True
1019 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, resolve_options)
1020 record_dicts = filter_records(options.type, record_dicts)
1021 if not record_dicts:
1022 self.logger.error("No record of type {}".format(options.type))
1024 # user has required to focus on some keys
1026 def project (record):
1028 for key in options.keys:
1029 try: projected[key]=record[key]
1032 record_dicts = [ project (record) for record in record_dicts ]
1033 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
1034 for record in records:
1035 if (options.format == "text"): record.dump(sort=True)
1036 else: print record.save_as_xml()
1038 save_records_to_file(options.file, record_dicts, options.fileformat)
1039 # xxx should analyze result
1042 # this historically was named 'add', it is now 'register' with an alias for legacy
1043 @declare_command("[xml-filename]","",['add'])
1044 def register(self, options, args):
1045 """create new record in registry (Register)
1046 from command line options (recommended)
1047 old-school method involving an xml file still supported"""
1049 auth_cred = self.my_authority_credential_string()
1050 if options.show_credential:
1051 show_credentials(auth_cred)
1058 record_filepath = args[0]
1059 rec_file = self.get_record_file(record_filepath)
1060 record_dict.update(load_record_from_file(rec_file).todict())
1062 print "Cannot load record file {}".format(record_filepath)
1065 record_dict.update(load_record_from_opts(options).todict())
1066 # we should have a type by now
1067 if 'type' not in record_dict :
1070 # this is still planetlab dependent.. as plc will whine without that
1071 # also, it's only for adding
1072 if record_dict['type'] == 'user':
1073 if not 'first_name' in record_dict:
1074 record_dict['first_name'] = record_dict['hrn']
1075 if 'last_name' not in record_dict:
1076 record_dict['last_name'] = record_dict['hrn']
1077 register = self.registry().Register(record_dict, auth_cred)
1078 # xxx looks like the result here is not ReturnValue-compatible
1079 #return self.success (register)
1080 # xxx should analyze result
1083 @declare_command("[xml-filename]","")
1084 def update(self, options, args):
1085 """update record into registry (Update)
1086 from command line options (recommended)
1087 old-school method involving an xml file still supported"""
1090 record_filepath = args[0]
1091 rec_file = self.get_record_file(record_filepath)
1092 record_dict.update(load_record_from_file(rec_file).todict())
1094 record_dict.update(load_record_from_opts(options).todict())
1095 # at the very least we need 'type' here
1096 if 'type' not in record_dict or record_dict['type'] is None:
1100 # don't translate into an object, as this would possibly distort
1101 # user-provided data; e.g. add an 'email' field to Users
1102 if record_dict['type'] in ['user']:
1103 if record_dict['hrn'] == self.user:
1104 cred = self.my_credential_string
1106 cred = self.my_authority_credential_string()
1107 elif record_dict['type'] in ['slice']:
1109 cred = self.slice_credential_string(record_dict['hrn'])
1110 except ServerException, e:
1111 # XXX smbaker -- once we have better error return codes, update this
1112 # to do something better than a string compare
1113 if "Permission error" in e.args[0]:
1114 cred = self.my_authority_credential_string()
1117 elif record_dict['type'] in ['authority']:
1118 cred = self.my_authority_credential_string()
1119 elif record_dict['type'] in ['node']:
1120 cred = self.my_authority_credential_string()
1122 raise Exception("unknown record type {}".format(record_dict['type']))
1123 if options.show_credential:
1124 show_credentials(cred)
1125 update = self.registry().Update(record_dict, cred)
1126 # xxx looks like the result here is not ReturnValue-compatible
1127 #return self.success(update)
1128 # xxx should analyze result
1131 @declare_command("hrn","")
1132 def remove(self, options, args):
1133 "remove registry record by name (Remove)"
1134 auth_cred = self.my_authority_credential_string()
1142 if options.show_credential:
1143 show_credentials(auth_cred)
1144 remove = self.registry().Remove(hrn, auth_cred, type)
1145 # xxx looks like the result here is not ReturnValue-compatible
1146 #return self.success (remove)
1147 # xxx should analyze result
1150 # ==================================================================
1151 # Slice-related commands
1152 # ==================================================================
1154 # show rspec for named slice
1155 @declare_command("","",['discover'])
1156 def resources(self, options, args):
1158 discover available resources (ListResources)
1160 server = self.sliceapi()
1163 creds = [self.my_credential]
1164 if options.delegate:
1165 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1166 if options.show_credential:
1167 show_credentials(creds)
1169 # no need to check if server accepts the options argument since the options has
1170 # been a required argument since v1 API
1172 # always send call_id to v2 servers
1173 api_options ['call_id'] = unique_call_id()
1174 # ask for cached value if available
1175 api_options ['cached'] = True
1177 api_options['info'] = options.info
1178 if options.list_leases:
1179 api_options['list_leases'] = options.list_leases
1181 if options.current == True:
1182 api_options['cached'] = False
1184 api_options['cached'] = True
1185 if options.rspec_version:
1186 version_manager = VersionManager()
1187 server_version = self.get_cached_server_version(server)
1188 if 'sfa' in server_version:
1189 # just request the version the client wants
1190 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1192 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1194 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1195 list_resources = server.ListResources (creds, api_options)
1196 value = ReturnValue.get_value(list_resources)
1197 if self.options.raw:
1198 save_raw_to_file(list_resources, self.options.raw, self.options.rawformat, self.options.rawbanner)
1199 if options.file is not None:
1200 save_rspec_to_file(value, options.file)
1201 if (self.options.raw is None) and (options.file is None):
1202 display_rspec(value, options.format)
1203 return self.success(list_resources)
1205 @declare_command("slice_hrn","")
1206 def describe(self, options, args):
1208 shows currently allocated/provisioned resources
1209 of the named slice or set of slivers (Describe)
1211 server = self.sliceapi()
1214 creds = [self.slice_credential(args[0])]
1215 if options.delegate:
1216 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1217 if options.show_credential:
1218 show_credentials(creds)
1220 api_options = {'call_id': unique_call_id(),
1222 'info': options.info,
1223 'list_leases': options.list_leases,
1224 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1227 api_options['info'] = options.info
1229 if options.rspec_version:
1230 version_manager = VersionManager()
1231 server_version = self.get_cached_server_version(server)
1232 if 'sfa' in server_version:
1233 # just request the version the client wants
1234 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1236 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1237 urn = Xrn(args[0], type='slice').get_urn()
1238 remove_none_fields(api_options)
1239 describe = server.Describe([urn], creds, api_options)
1240 value = ReturnValue.get_value(describe)
1241 if self.options.raw:
1242 save_raw_to_file(describe, self.options.raw, self.options.rawformat, self.options.rawbanner)
1243 if options.file is not None:
1244 save_rspec_to_file(value['geni_rspec'], options.file)
1245 if (self.options.raw is None) and (options.file is None):
1246 display_rspec(value['geni_rspec'], options.format)
1247 return self.success (describe)
1249 @declare_command("slice_hrn [<sliver_urn>...]","")
1250 def delete(self, options, args):
1252 de-allocate and de-provision all or named slivers of the named slice (Delete)
1254 server = self.sliceapi()
1258 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1261 # we have sliver urns
1262 sliver_urns = args[1:]
1264 # we provision all the slivers of the slice
1265 sliver_urns = [slice_urn]
1268 slice_cred = self.slice_credential(slice_hrn)
1269 creds = [slice_cred]
1271 # options and call_id when supported
1273 api_options ['call_id'] = unique_call_id()
1274 if options.show_credential:
1275 show_credentials(creds)
1276 delete = server.Delete(sliver_urns, creds, *self.ois(server, api_options ) )
1277 value = ReturnValue.get_value(delete)
1278 if self.options.raw:
1279 save_raw_to_file(delete, self.options.raw, self.options.rawformat, self.options.rawbanner)
1282 return self.success (delete)
1284 @declare_command("slice_hrn rspec","")
1285 def allocate(self, options, args):
1287 allocate resources to the named slice (Allocate)
1289 server = self.sliceapi()
1290 server_version = self.get_cached_server_version(server)
1295 rspec_file = self.get_rspec_file(args[1])
1297 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1300 creds = [self.slice_credential(slice_hrn)]
1302 delegated_cred = None
1303 if server_version.get('interface') == 'slicemgr':
1304 # delegate our cred to the slice manager
1305 # do not delegate cred to slicemgr...not working at the moment
1307 #if server_version.get('hrn'):
1308 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1309 #elif server_version.get('urn'):
1310 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1312 if options.show_credential:
1313 show_credentials(creds)
1317 api_options ['call_id'] = unique_call_id()
1321 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1322 remove_none_fields(slice_records[0])
1323 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1324 slice_record = slice_records[0]
1325 user_hrns = slice_record['reg-researchers']
1326 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1327 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1328 sfa_users = sfa_users_arg(user_records, slice_record)
1329 geni_users = pg_users_arg(user_records)
1331 api_options['sfa_users'] = sfa_users
1332 api_options['geni_users'] = geni_users
1334 with open(rspec_file) as rspec:
1335 rspec_xml = rspec.read()
1336 allocate = server.Allocate(slice_urn, creds, rspec_xml, api_options)
1337 value = ReturnValue.get_value(allocate)
1338 if self.options.raw:
1339 save_raw_to_file(allocate, self.options.raw, self.options.rawformat, self.options.rawbanner)
1340 if options.file is not None:
1341 save_rspec_to_file (value['geni_rspec'], options.file)
1342 if (self.options.raw is None) and (options.file is None):
1344 return self.success(allocate)
1346 @declare_command("slice_hrn [<sliver_urn>...]","")
1347 def provision(self, options, args):
1349 provision all or named already allocated slivers of the named slice (Provision)
1351 server = self.sliceapi()
1352 server_version = self.get_cached_server_version(server)
1354 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1356 # we have sliver urns
1357 sliver_urns = args[1:]
1359 # we provision all the slivers of the slice
1360 sliver_urns = [slice_urn]
1363 creds = [self.slice_credential(slice_hrn)]
1364 delegated_cred = None
1365 if server_version.get('interface') == 'slicemgr':
1366 # delegate our cred to the slice manager
1367 # do not delegate cred to slicemgr...not working at the moment
1369 #if server_version.get('hrn'):
1370 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1371 #elif server_version.get('urn'):
1372 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1374 if options.show_credential:
1375 show_credentials(creds)
1378 api_options ['call_id'] = unique_call_id()
1380 # set the requtested rspec version
1381 version_manager = VersionManager()
1382 rspec_version = version_manager._get_version('geni', '3').to_dict()
1383 api_options['geni_rspec_version'] = rspec_version
1386 # need to pass along user keys to the aggregate.
1388 # { urn: urn:publicid:IDN+emulab.net+user+alice
1389 # keys: [<ssh key A>, <ssh key B>]
1392 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1393 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1394 slice_record = slice_records[0]
1395 user_hrns = slice_record['reg-researchers']
1396 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1397 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1398 users = pg_users_arg(user_records)
1400 api_options['geni_users'] = users
1401 provision = server.Provision(sliver_urns, creds, api_options)
1402 value = ReturnValue.get_value(provision)
1403 if self.options.raw:
1404 save_raw_to_file(provision, self.options.raw, self.options.rawformat, self.options.rawbanner)
1405 if options.file is not None:
1406 save_rspec_to_file (value['geni_rspec'], options.file)
1407 if (self.options.raw is None) and (options.file is None):
1409 return self.success(provision)
1411 @declare_command("slice_hrn","")
1412 def status(self, options, args):
1414 retrieve the status of the slivers belonging to the named slice (Status)
1416 server = self.sliceapi()
1420 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1423 slice_cred = self.slice_credential(slice_hrn)
1424 creds = [slice_cred]
1426 # options and call_id when supported
1428 api_options['call_id']=unique_call_id()
1429 if options.show_credential:
1430 show_credentials(creds)
1431 status = server.Status([slice_urn], creds, *self.ois(server,api_options))
1432 value = ReturnValue.get_value(status)
1433 if self.options.raw:
1434 save_raw_to_file(status, self.options.raw, self.options.rawformat, self.options.rawbanner)
1437 return self.success (status)
1439 @declare_command("slice_hrn [<sliver_urn>...] action","")
1440 def action(self, options, args):
1442 Perform the named operational action on all or named slivers of the named slice
1444 server = self.sliceapi()
1448 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1450 # we have sliver urns
1451 sliver_urns = args[1:-1]
1453 # we provision all the slivers of the slice
1454 sliver_urns = [slice_urn]
1457 slice_cred = self.slice_credential(args[0])
1458 creds = [slice_cred]
1459 if options.delegate:
1460 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1461 creds.append(delegated_cred)
1463 perform_action = server.PerformOperationalAction(sliver_urns, creds, action , api_options)
1464 value = ReturnValue.get_value(perform_action)
1465 if self.options.raw:
1466 save_raw_to_file(perform_action, self.options.raw, self.options.rawformat, self.options.rawbanner)
1469 return self.success (perform_action)
1471 @declare_command("slice_hrn [<sliver_urn>...] time",
1472 "\n".join(["sfi renew onelab.ple.heartbeat 2015-04-31",
1473 "sfi renew onelab.ple.heartbeat 2015-04-31T14:00:00Z",
1474 "sfi renew onelab.ple.heartbeat +5d",
1475 "sfi renew onelab.ple.heartbeat +3w",
1476 "sfi renew onelab.ple.heartbeat +2m",]))
1477 def renew(self, options, args):
1481 server = self.sliceapi()
1486 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1489 # we have sliver urns
1490 sliver_urns = args[1:-1]
1492 # we provision all the slivers of the slice
1493 sliver_urns = [slice_urn]
1494 input_time = args[-1]
1496 # time: don't try to be smart on the time format, server-side will
1498 slice_cred = self.slice_credential(args[0])
1499 creds = [slice_cred]
1500 # options and call_id when supported
1502 api_options['call_id']=unique_call_id()
1504 api_options['geni_extend_alap']=True
1505 if options.show_credential:
1506 show_credentials(creds)
1507 renew = server.Renew(sliver_urns, creds, input_time, *self.ois(server,api_options))
1508 value = ReturnValue.get_value(renew)
1509 if self.options.raw:
1510 save_raw_to_file(renew, self.options.raw, self.options.rawformat, self.options.rawbanner)
1513 return self.success(renew)
1515 @declare_command("slice_hrn","")
1516 def shutdown(self, options, args):
1518 shutdown named slice (Shutdown)
1520 server = self.sliceapi()
1523 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1525 slice_cred = self.slice_credential(slice_hrn)
1526 creds = [slice_cred]
1527 shutdown = server.Shutdown(slice_urn, creds)
1528 value = ReturnValue.get_value(shutdown)
1529 if self.options.raw:
1530 save_raw_to_file(shutdown, self.options.raw, self.options.rawformat, self.options.rawbanner)
1533 return self.success (shutdown)
1535 @declare_command("[name]","")
1536 def gid(self, options, args):
1538 Create a GID (CreateGid)
1543 target_hrn = args[0]
1544 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1545 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1547 filename = options.file
1549 filename = os.sep.join([self.options.sfi_dir, '{}.gid'.format(target_hrn)])
1550 self.logger.info("writing {} gid to {}".format(target_hrn, filename))
1551 GID(string=gid).save_to_file(filename)
1552 # xxx should analyze result
1555 ####################
1556 @declare_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1558 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1559 the set of credentials in the scope for this call would be
1560 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1562 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1564 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1565 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1566 because of the two -s options
1569 def delegate (self, options, args):
1571 (locally) create delegate credential for use by given hrn
1572 make sure to check for 'sfi myslice' instead if you plan
1579 # support for several delegations in the same call
1580 # so first we gather the things to do
1582 for slice_hrn in options.delegate_slices:
1583 message = "{}.slice".format(slice_hrn)
1584 original = self.slice_credential_string(slice_hrn)
1585 tuples.append ( (message, original,) )
1586 if options.delegate_pi:
1587 my_authority=self.authority
1588 message = "{}.pi".format(my_authority)
1589 original = self.my_authority_credential_string()
1590 tuples.append ( (message, original,) )
1591 for auth_hrn in options.delegate_auths:
1592 message = "{}.auth".format(auth_hrn)
1593 original = self.authority_credential_string(auth_hrn)
1594 tuples.append ( (message, original, ) )
1595 # if nothing was specified at all at this point, let's assume -u
1596 if not tuples: options.delegate_user=True
1598 if options.delegate_user:
1599 message = "{}.user".format(self.user)
1600 original = self.my_credential_string
1601 tuples.append ( (message, original, ) )
1603 # default type for beneficial is user unless -A
1604 if options.delegate_to_authority: to_type='authority'
1605 else: to_type='user'
1607 # let's now handle all this
1608 # it's all in the filenaming scheme
1609 for (message,original) in tuples:
1610 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1611 delegated_credential = Credential (string=delegated_string)
1612 filename = os.path.join(self.options.sfi_dir,
1613 "{}_for_{}.{}.cred".format(message, to_hrn, to_type))
1614 delegated_credential.save_to_file(filename, save_parents=True)
1615 self.logger.info("delegated credential for {} to {} and wrote to {}"
1616 .format(message, to_hrn, filename))
1618 ####################
1619 @declare_command("","""$ less +/myslice sfi_config
1621 backend = http://manifold.pl.sophia.inria.fr:7080
1622 # the HRN that myslice uses, so that we are delegating to
1623 delegate = ple.upmc.slicebrowser
1624 # platform - this is a myslice concept
1626 # username - as of this writing (May 2013) a simple login name
1630 will first collect the slices that you are part of, then make sure
1631 all your credentials are up-to-date (read: refresh expired ones)
1632 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1633 and upload them all on myslice backend, using 'platform' and 'user'.
1634 A password will be prompted for the upload part.
1636 $ sfi -v myslice -- or sfi -vv myslice
1637 same but with more and more verbosity
1639 $ sfi m -b http://mymanifold.foo.com:7080/
1640 is synonym to sfi myslice as no other command starts with an 'm'
1641 and uses a custom backend for this one call
1644 def myslice (self, options, args):
1646 """ This helper is for refreshing your credentials at myslice; it will
1647 * compute all the slices that you currently have credentials on
1648 * refresh all your credentials (you as a user and pi, your slices)
1649 * upload them to the manifold backend server
1650 for last phase, sfi_config is read to look for the [myslice] section,
1651 and namely the 'backend', 'delegate' and 'user' settings"""
1657 # enable info by default
1658 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1659 ### the rough sketch goes like this
1660 # (0) produce a p12 file
1661 self.client_bootstrap.my_pkcs12()
1663 # (a) rain check for sufficient config in sfi_config
1665 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1666 for key in myslice_keys:
1668 # oct 2013 - I'm finding myself juggling with config files
1669 # so a couple of command-line options can now override config
1670 if hasattr(options,key) and getattr(options,key) is not None:
1671 value=getattr(options,key)
1673 full_key="MYSLICE_" + key.upper()
1674 value=getattr(self.config_instance,full_key,None)
1676 myslice_dict[key]=value
1678 print "Unsufficient config, missing key {} in [myslice] section of sfi_config"\
1680 if len(myslice_dict) != len(myslice_keys):
1683 # (b) figure whether we are PI for the authority where we belong
1684 self.logger.info("Resolving our own id {}".format(self.user))
1685 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1686 if len(my_records) != 1:
1687 print "Cannot Resolve {} -- exiting".format(self.user)
1689 my_record = my_records[0]
1690 my_auths_all = my_record['reg-pi-authorities']
1691 self.logger.info("Found {} authorities that we are PI for".format(len(my_auths_all)))
1692 self.logger.debug("They are {}".format(my_auths_all))
1694 my_auths = my_auths_all
1695 if options.delegate_auths:
1696 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1697 self.logger.debug("Restricted to user-provided auths {}".format(my_auths))
1699 # (c) get the set of slices that we are in
1700 my_slices_all=my_record['reg-slices']
1701 self.logger.info("Found {} slices that we are member of".format(len(my_slices_all)))
1702 self.logger.debug("They are: {}".format(my_slices_all))
1704 my_slices = my_slices_all
1705 # if user provided slices, deal only with these - if they are found
1706 if options.delegate_slices:
1707 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1708 self.logger.debug("Restricted to user-provided slices: {}".format(my_slices))
1710 # (d) make sure we have *valid* credentials for all these
1712 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1713 for auth_hrn in my_auths:
1714 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1715 for slice_hrn in my_slices:
1716 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1718 # (e) check for the delegated version of these
1719 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1720 # switch to myslice using an authority instead of a user
1721 delegatee_type='user'
1722 delegatee_hrn=myslice_dict['delegate']
1723 hrn_delegated_credentials = []
1724 for (hrn, htype, credential) in hrn_credentials:
1725 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1726 # save these so user can monitor what she's uploaded
1727 filename = os.path.join ( self.options.sfi_dir,
1728 "{}.{}_for_{}.{}.cred"\
1729 .format(hrn, htype, delegatee_hrn, delegatee_type))
1730 with file(filename,'w') as f:
1731 f.write(delegated_credential)
1732 self.logger.debug("(Over)wrote {}".format(filename))
1733 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1735 # (f) and finally upload them to manifold server
1736 # xxx todo add an option so the password can be set on the command line
1737 # (but *NOT* in the config file) so other apps can leverage this
1738 self.logger.info("Uploading on backend at {}".format(myslice_dict['backend']))
1739 uploader = ManifoldUploader (logger=self.logger,
1740 url=myslice_dict['backend'],
1741 platform=myslice_dict['platform'],
1742 username=myslice_dict['username'],
1743 password=options.password)
1744 uploader.prompt_all()
1745 (count_all,count_success)=(0,0)
1746 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1748 inspect=Credential(string=delegated_credential)
1749 expire_datetime=inspect.get_expiration()
1750 message="{} ({}) [exp:{}]".format(hrn, htype, expire_datetime)
1751 if uploader.upload(delegated_credential,message=message):
1754 self.logger.info("Successfully uploaded {}/{} credentials"
1755 .format(count_success, count_all))
1757 # at first I thought we would want to save these,
1758 # like 'sfi delegate does' but on second thought
1759 # it is probably not helpful as people would not
1760 # need to run 'sfi delegate' at all anymore
1761 if count_success != count_all: sys.exit(1)
1762 # xxx should analyze result
1765 @declare_command("cred","")
1766 def trusted(self, options, args):
1768 return the trusted certs at this interface (get_trusted_certs)
1770 if options.registry_interface:
1771 server=self.registry()
1773 server = self.sliceapi()
1774 cred = self.my_authority_credential_string()
1775 trusted_certs = server.get_trusted_certs(cred)
1776 if not options.registry_interface:
1777 trusted_certs = ReturnValue.get_value(trusted_certs)
1779 for trusted_cert in trusted_certs:
1780 print "\n===========================================================\n"
1781 gid = GID(string=trusted_cert)
1783 cert = Certificate(string=trusted_cert)
1784 self.logger.debug('Sfi.trusted -> {}'.format(cert.get_subject()))
1785 print "Certificate:\n{}\n\n".format(trusted_cert)
1786 # xxx should analyze result