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))
115 ########## save methods
118 def save_raw_to_file(var, filename, format='text', banner=None):
120 _save_raw_to_file(var, sys.stdout, format, banner)
122 with open(filename, w) as fileobj:
123 _save_raw_to_file(var, fileobj, format, banner)
124 print "(Over)wrote {}".format(filename)
126 def _save_raw_to_file(var, f, format, banner):
128 if banner: f.write(banner+"\n")
129 f.write("{}".format(var))
130 if banner: f.write('\n'+banner+"\n")
131 elif format == "pickled":
132 f.write(pickle.dumps(var))
133 elif format == "json":
134 f.write(json.dumps(var)) # python 2.6
136 # this should never happen
137 print "unknown output format", format
140 def save_rspec_to_file(rspec, filename):
141 if not filename.endswith(".rspec"):
142 filename = filename + ".rspec"
143 with open(filename, 'w') as f:
144 f.write("{}".format(rspec))
145 print "(Over)wrote {}".format(filename)
147 def save_record_to_file(filename, record_dict):
148 record = Record(dict=record_dict)
149 xml = record.save_as_xml()
150 with codecs.open(filename, encoding='utf-8',mode="w") as f:
152 print "(Over)wrote {}".format(filename)
154 def save_records_to_file(filename, record_dicts, format="xml"):
156 for index, record_dict in enumerate(record_dicts):
157 save_record_to_file(filename + "." + str(index), record_dict)
158 elif format == "xmllist":
159 with open(filename, "w") as f:
160 f.write("<recordlist>\n")
161 for record_dict in record_dicts:
162 record_obj = Record(dict=record_dict)
163 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
164 f.write("</recordlist>\n")
165 print "(Over)wrote {}".format(filename)
167 elif format == "hrnlist":
168 with open(filename, "w") as f:
169 for record_dict in record_dicts:
170 record_obj = Record(dict=record_dict)
171 f.write(record_obj.hrn + "\n")
172 print "(Over)wrote {}".format(filename)
175 # this should never happen
176 print "unknown output format", format
178 # minimally check a key argument
179 def check_ssh_key (key):
180 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
181 return re.match(good_ssh_key, key, re.IGNORECASE)
184 def normalize_type (type):
185 if type.startswith('au'):
187 elif type.startswith('us'):
189 elif type.startswith('sl'):
191 elif type.startswith('no'):
193 elif type.startswith('ag'):
195 elif type.startswith('al'):
198 print 'unknown type {} - should start with one of au|us|sl|no|ag|al'.format(type)
201 def load_record_from_opts(options):
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 with codecs.open(filename, encoding="utf-8", mode="r") as f:
239 return Record(xml=xml_str)
242 def unique_call_id(): return uuid.uuid4().urn
244 ########## a simple model for maintaing 3 doc attributes per command (instead of just one)
245 # essentially for the methods that implement a subcommand like sfi list
246 # we need to keep track of
247 # (*) doc a few lines that tell what it does, still located in __doc__
248 # (*) args_string a simple one-liner that describes mandatory arguments
249 # (*) example well, one or several releant examples
251 # since __doc__ only accounts for one, we use this simple mechanism below
252 # however we keep doc in place for easier migration
254 from functools import wraps
256 # we use a list as well as a dict so we can keep track of the order
260 def declare_command (args_string, example,aliases=None):
262 name=getattr(m,'__name__')
263 doc=getattr(m,'__doc__',"-- missing doc --")
264 doc=doc.strip(" \t\n")
265 commands_list.append(name)
266 # last item is 'canonical' name, so we can know which commands are aliases
267 command_tuple=(doc, args_string, example,name)
268 commands_dict[name]=command_tuple
269 if aliases is not None:
270 for alias in aliases:
271 commands_list.append(alias)
272 commands_dict[alias]=command_tuple
274 def new_method (*args, **kwds): return m(*args, **kwds)
279 def remove_none_fields (record):
280 none_fields=[ k for (k,v) in record.items() if v is None ]
281 for k in none_fields: del record[k]
287 # dirty hack to make this class usable from the outside
288 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
291 def default_sfi_dir ():
292 if os.path.isfile("./sfi_config"):
295 return os.path.expanduser("~/.sfi/")
297 # dummy to meet Sfi's expectations for its 'options' field
298 # i.e. s/t we can do setattr on
302 def __init__ (self,options=None):
303 if options is None: options=Sfi.DummyOptions()
304 for opt in Sfi.required_options:
305 if not hasattr(options,opt): setattr(options,opt,None)
306 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
307 self.options = options
309 self.authority = None
310 self.logger = sfi_logger
311 self.logger.enable_console()
312 ### various auxiliary material that we keep at hand
314 # need to call this other than just 'config' as we have a command/method with that name
315 self.config_instance=None
316 self.config_file=None
317 self.client_bootstrap=None
319 ### suitable if no reasonable command has been provided
320 def print_commands_help (self, options):
321 verbose=getattr(options,'verbose')
322 format3="%10s %-35s %s"
326 print format3%("command", "cmd_args", "description")
330 self.create_parser_global().print_help()
331 # preserve order from the code
332 for command in commands_list:
334 (doc, args_string, example, canonical) = commands_dict[command]
336 print "Cannot find info on command %s - skipped"%command
340 if command==canonical:
341 doc = doc.replace("\n", "\n" + format3offset * ' ')
342 print format3 % (command,args_string,doc)
344 self.create_parser_command(command).print_help()
346 print format3 % (command,"<<alias for %s>>"%canonical,"")
348 ### now if a known command was found we can be more verbose on that one
349 def print_help (self):
350 print "==================== Generic sfi usage"
351 self.sfi_parser.print_help()
352 (doc, _, example, canonical) = commands_dict[self.command]
353 if canonical != self.command:
354 print "\n==================== NOTE: {} is an alias for genuine {}"\
355 .format(self.command, canonical)
356 self.command = canonical
357 print "\n==================== Purpose of {}".format(self.command)
359 print "\n==================== Specific usage for {}".format(self.command)
360 self.command_parser.print_help()
362 print "\n==================== {} example(s)".format(self.command)
365 def create_parser_global(self):
366 # Generate command line parser
367 parser = OptionParser(add_help_option=False,
368 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
369 description="Commands: {}".format(" ".join(commands_list)))
370 parser.add_option("-r", "--registry", dest="registry",
371 help="root registry", metavar="URL", default=None)
372 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
373 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
374 parser.add_option("-R", "--raw", dest="raw", default=None,
375 help="Save raw, unparsed server response to a file")
376 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
377 help="raw file format ([text]|pickled|json)", default="text",
378 choices=("text","pickled","json"))
379 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
380 help="text string to write before and after raw output")
381 parser.add_option("-d", "--dir", dest="sfi_dir",
382 help="config & working directory - default is %default",
383 metavar="PATH", default=Sfi.default_sfi_dir())
384 parser.add_option("-u", "--user", dest="user",
385 help="user name", metavar="HRN", default=None)
386 parser.add_option("-a", "--auth", dest="auth",
387 help="authority name", metavar="HRN", default=None)
388 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
389 help="verbose mode - cumulative")
390 parser.add_option("-D", "--debug",
391 action="store_true", dest="debug", default=False,
392 help="Debug (xml-rpc) protocol messages")
393 # would it make sense to use ~/.ssh/id_rsa as a default here ?
394 parser.add_option("-k", "--private-key",
395 action="store", dest="user_private_key", default=None,
396 help="point to the private key file to use if not yet installed in sfi_dir")
397 parser.add_option("-t", "--timeout", dest="timeout", default=None,
398 help="Amout of time to wait before timing out the request")
399 parser.add_option("-h", "--help",
400 action="store_true", dest="help", default=False,
401 help="one page summary on commands & exit")
402 parser.disable_interspersed_args()
407 def create_parser_command(self, command):
408 if command not in commands_dict:
409 msg="Invalid command\n"
411 msg += ','.join(commands_list)
412 self.logger.critical(msg)
415 # retrieve args_string
416 (_, args_string, __,canonical) = commands_dict[command]
418 parser = OptionParser(add_help_option=False,
419 usage="sfi [sfi_options] {} [cmd_options] {}"\
420 .format(command, args_string))
421 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
422 help="Summary of one command usage")
424 if canonical in ("config"):
425 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
426 help='how myslice config variables as well')
428 if canonical in ("version"):
429 parser.add_option("-l","--local",
430 action="store_true", dest="version_local", default=False,
431 help="display version of the local client")
433 if canonical in ("version", "trusted"):
434 parser.add_option("-R","--registry_interface",
435 action="store_true", dest="registry_interface", default=False,
436 help="target the registry interface instead of slice interface")
438 if canonical in ("register", "update"):
439 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
440 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type (2 first chars is enough)', default=None)
441 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
442 parser.add_option('-n', '--name', dest='name', default="", help="name (optional for authorities)")
443 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
445 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
446 default='', type="str", action='callback', callback=optparse_listvalue_callback)
447 parser.add_option('-r', '--researchers', dest='reg_researchers', metavar='<researchers>',
448 help='Set/replace slice researchers - use -r none to reset', default=None, type="str", action='callback',
449 callback=optparse_listvalue_callback)
450 parser.add_option('-p', '--pis', dest='reg_pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
451 default='', type="str", action='callback', callback=optparse_listvalue_callback)
452 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
453 action="callback", callback=optparse_dictvalue_callback, nargs=1,
454 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
456 # user specifies remote aggregate/sm/component
457 if canonical in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
458 "action", "shutdown", "renew", "status"):
459 parser.add_option("-d", "--delegate", dest="delegate", default=None,
461 help="Include a credential delegated to the user's root"+\
462 "authority in set of credentials for this call")
464 # show_credential option
465 if canonical in ("list","resources", "describe", "provision", "allocate", "register","update","remove","delete","status","renew"):
466 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
467 help="show credential(s) used in human-readable form")
468 if canonical in ("renew"):
469 parser.add_option("-l","--as-long-as-possible",dest='alap',action='store_true',default=False,
470 help="renew as long as possible")
471 # registy filter option
472 if canonical in ("list", "show", "remove"):
473 parser.add_option("-t", "--type", dest="type", metavar="<type>",
475 help="type filter - 2 first chars is enough ([all]|user|slice|authority|node|aggregate)")
476 if canonical in ("show"):
477 parser.add_option("-k","--key",dest="keys",action="append",default=[],
478 help="specify specific keys to be displayed from record")
479 parser.add_option("-n","--no-details",dest="no_details",action="store_true",default=False,
480 help="call Resolve without the 'details' option")
481 if canonical in ("resources", "describe"):
483 parser.add_option("-r", "--rspec-version", dest="rspec_version", default=DEFAULT_RSPEC_VERSION,
484 help="schema type and version of resulting RSpec (default:{})".format(DEFAULT_RSPEC_VERSION))
485 # disable/enable cached rspecs
486 parser.add_option("-c", "--current", dest="current", default=False,
488 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
490 parser.add_option("-f", "--format", dest="format", type="choice",
491 help="display format ([xml]|dns|ip)", default="xml",
492 choices=("xml", "dns", "ip"))
493 #panos: a new option to define the type of information about resources a user is interested in
494 parser.add_option("-i", "--info", dest="info",
495 help="optional component information", default=None)
496 # a new option to retrieve or not reservation-oriented RSpecs (leases)
497 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
498 help="Retrieve or not reservation-oriented RSpecs ([resources]|leases|all)",
499 choices=("all", "resources", "leases"), default="resources")
502 if canonical in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
503 parser.add_option("-o", "--output", dest="file",
504 help="output XML to file", metavar="FILE", default=None)
506 if canonical in ("show", "list"):
507 parser.add_option("-f", "--format", dest="format", type="choice",
508 help="display format ([text]|xml)", default="text",
509 choices=("text", "xml"))
511 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
512 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
513 choices=("xml", "xmllist", "hrnlist"))
514 if canonical == 'list':
515 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
516 help="list all child records", default=False)
517 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
518 help="gives details, like user keys", default=False)
519 if canonical in ("delegate"):
520 parser.add_option("-u", "--user",
521 action="store_true", dest="delegate_user", default=False,
522 help="delegate your own credentials; default if no other option is provided")
523 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
524 metavar="slice_hrn", help="delegate cred. for slice HRN")
525 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
526 metavar='auth_hrn', help="delegate cred for auth HRN")
527 # this primarily is a shorthand for -A my_hrn^
528 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
529 help="delegate your PI credentials, so s.t. like -A your_hrn^")
530 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
531 help="""by default the mandatory argument is expected to be a user,
532 use this if you mean an authority instead""")
534 if canonical in ("myslice"):
535 parser.add_option("-p","--password",dest='password',action='store',default=None,
536 help="specify mainfold password on the command line")
537 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
538 metavar="slice_hrn", help="delegate cred. for slice HRN")
539 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
540 metavar='auth_hrn', help="delegate PI cred for auth HRN")
541 parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
542 parser.add_option('-b', '--backend', dest='backend', help="Override 'backend' from the config file")
548 # Main: parse arguments and dispatch to command
550 def dispatch(self, command, command_options, command_args):
551 (doc, args_string, example, canonical) = commands_dict[command]
552 method=getattr(self, canonical, None)
554 print "sfi: unknown command {}".format(command)
555 raise SystemExit("Unknown command {}".format(command))
556 for arg in command_args:
557 if 'help' in arg or arg == '-h':
560 return method(command_options, command_args)
563 self.sfi_parser = self.create_parser_global()
564 (options, args) = self.sfi_parser.parse_args()
566 self.print_commands_help(options)
568 self.options = options
570 self.logger.setLevelFromOptVerbose(self.options.verbose)
573 self.logger.critical("No command given. Use -h for help.")
574 self.print_commands_help(options)
577 # complete / find unique match with command set
578 command_candidates = Candidates (commands_list)
580 command = command_candidates.only_match(input)
582 self.print_commands_help(options)
584 # second pass options parsing
586 self.command_parser = self.create_parser_command(command)
587 (command_options, command_args) = self.command_parser.parse_args(args[1:])
588 if command_options.help:
591 self.command_options = command_options
593 # allow incoming types on 2 characters only
594 if hasattr(command_options, 'type'):
595 command_options.type = normalize_type(command_options.type)
596 if not command_options.type:
601 self.logger.debug("Command={}".format(self.command))
604 retcod = self.dispatch(command, command_options, command_args)
608 self.logger.log_exc ("sfi command {} failed".format(command))
613 def read_config(self):
614 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
615 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
617 if Config.is_ini(config_file):
618 config = Config (config_file)
620 # try upgrading from shell config format
621 fp, fn = mkstemp(suffix='sfi_config', text=True)
623 # we need to preload the sections we want parsed
624 # from the shell config
625 config.add_section('sfi')
626 # sface users should be able to use this same file to configure their stuff
627 config.add_section('sface')
628 # manifold users should be able to specify the details
629 # of their backend server here for 'sfi myslice'
630 config.add_section('myslice')
631 config.load(config_file)
633 shutil.move(config_file, shell_config_file)
635 config.save(config_file)
638 self.logger.critical("Failed to read configuration file {}".format(config_file))
639 self.logger.info("Make sure to remove the export clauses and to add quotes")
640 if self.options.verbose==0:
641 self.logger.info("Re-run with -v for more details")
643 self.logger.log_exc("Could not read config file {}".format(config_file))
646 self.config_instance=config
649 if (self.options.sm is not None):
650 self.sm_url = self.options.sm
651 elif hasattr(config, "SFI_SM"):
652 self.sm_url = config.SFI_SM
654 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in {}".format(config_file))
658 if (self.options.registry is not None):
659 self.reg_url = self.options.registry
660 elif hasattr(config, "SFI_REGISTRY"):
661 self.reg_url = config.SFI_REGISTRY
663 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in {}".format(config_file))
667 if (self.options.user is not None):
668 self.user = self.options.user
669 elif hasattr(config, "SFI_USER"):
670 self.user = config.SFI_USER
672 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in {}".format(config_file))
676 if (self.options.auth is not None):
677 self.authority = self.options.auth
678 elif hasattr(config, "SFI_AUTH"):
679 self.authority = config.SFI_AUTH
681 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in {}".format(config_file))
684 self.config_file=config_file
689 # Get various credential and spec files
691 # Establishes limiting conventions
692 # - conflates MAs and SAs
693 # - assumes last token in slice name is unique
695 # Bootstraps credentials
696 # - bootstrap user credential from self-signed certificate
697 # - bootstrap authority credential from user credential
698 # - bootstrap slice credential from user credential
701 # init self-signed cert, user credentials and gid
702 def bootstrap (self):
703 if self.options.verbose:
704 self.logger.info("Initializing SfaClientBootstrap with {}".format(self.reg_url))
705 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
707 # if -k is provided, use this to initialize private key
708 if self.options.user_private_key:
709 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
711 # trigger legacy compat code if needed
712 # the name has changed from just <leaf>.pkey to <hrn>.pkey
713 if not os.path.isfile(client_bootstrap.private_key_filename()):
714 self.logger.info ("private key not found, trying legacy name")
716 legacy_private_key = os.path.join (self.options.sfi_dir, "{}.pkey"
717 .format(Xrn.unescape(get_leaf(self.user))))
718 self.logger.debug("legacy_private_key={}"
719 .format(legacy_private_key))
720 client_bootstrap.init_private_key_if_missing (legacy_private_key)
721 self.logger.info("Copied private key from legacy location {}"
722 .format(legacy_private_key))
724 self.logger.log_exc("Can't find private key ")
728 client_bootstrap.bootstrap_my_gid()
729 # extract what's needed
730 self.private_key = client_bootstrap.private_key()
731 self.my_credential_string = client_bootstrap.my_credential_string ()
732 self.my_credential = {'geni_type': 'geni_sfa',
734 'geni_value': self.my_credential_string}
735 self.my_gid = client_bootstrap.my_gid ()
736 self.client_bootstrap = client_bootstrap
739 def my_authority_credential_string(self):
740 if not self.authority:
741 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
743 return self.client_bootstrap.authority_credential_string (self.authority)
745 def authority_credential_string(self, auth_hrn):
746 return self.client_bootstrap.authority_credential_string (auth_hrn)
748 def slice_credential_string(self, name):
749 return self.client_bootstrap.slice_credential_string (name)
751 def slice_credential(self, name):
752 return {'geni_type': 'geni_sfa',
754 'geni_value': self.slice_credential_string(name)}
756 # xxx should be supported by sfaclientbootstrap as well
757 def delegate_cred(self, object_cred, hrn, type='authority'):
758 # the gid and hrn of the object we are delegating
759 if isinstance(object_cred, str):
760 object_cred = Credential(string=object_cred)
761 object_gid = object_cred.get_gid_object()
762 object_hrn = object_gid.get_hrn()
764 if not object_cred.get_privileges().get_all_delegate():
765 self.logger.error("Object credential {} does not have delegate bit set"
769 # the delegating user's gid
770 caller_gidfile = self.my_gid()
772 # the gid of the user who will be delegated to
773 delegee_gid = self.client_bootstrap.gid(hrn,type)
774 delegee_hrn = delegee_gid.get_hrn()
775 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
776 return dcred.save_to_string(save_parents=True)
779 # Management of the servers
784 if not hasattr (self, 'registry_proxy'):
785 self.logger.info("Contacting Registry at: {}".format(self.reg_url))
786 self.registry_proxy \
787 = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
788 timeout=self.options.timeout, verbose=self.options.debug)
789 return self.registry_proxy
793 if not hasattr (self, 'sliceapi_proxy'):
794 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
795 if hasattr(self.command_options,'component') and self.command_options.component:
796 # resolve the hrn at the registry
797 node_hrn = self.command_options.component
798 records = self.registry().Resolve(node_hrn, self.my_credential_string)
799 records = filter_records('node', records)
801 self.logger.warning("No such component:{}".format(opts.component))
803 cm_url = "http://{}:{}/".format(record['hostname'], CM_PORT)
804 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
806 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
807 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
808 self.sm_url = 'http://' + self.sm_url
809 self.logger.info("Contacting Slice Manager at: {}".format(self.sm_url))
810 self.sliceapi_proxy \
811 = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
812 timeout=self.options.timeout, verbose=self.options.debug)
813 return self.sliceapi_proxy
815 def get_cached_server_version(self, server):
816 # check local cache first
819 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
820 cache_key = server.url + "-version"
822 cache = Cache(cache_file)
825 self.logger.info("Local cache not found at: {}".format(cache_file))
828 version = cache.get(cache_key)
831 result = server.GetVersion()
832 version= ReturnValue.get_value(result)
833 # cache version for 20 minutes
834 cache.add(cache_key, version, ttl= 60*20)
835 self.logger.info("Updating cache file {}".format(cache_file))
836 cache.save_to_file(cache_file)
840 ### resurrect this temporarily so we can support V1 aggregates for a while
841 def server_supports_options_arg(self, server):
843 Returns true if server support the optional call_id arg, false otherwise.
845 server_version = self.get_cached_server_version(server)
847 # xxx need to rewrite this
848 if int(server_version.get('geni_api')) >= 2:
852 def server_supports_call_id_arg(self, server):
853 server_version = self.get_cached_server_version(server)
855 if 'sfa' in server_version and 'code_tag' in server_version:
856 code_tag = server_version['code_tag']
857 code_tag_parts = code_tag.split("-")
858 version_parts = code_tag_parts[0].split(".")
859 major, minor = version_parts[0], version_parts[1]
860 rev = code_tag_parts[1]
861 if int(major) == 1 and minor == 0 and build >= 22:
865 ### ois = options if supported
866 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
867 def ois (self, server, option_dict):
868 if self.server_supports_options_arg (server):
870 elif self.server_supports_call_id_arg (server):
871 return [ unique_call_id () ]
875 ### cis = call_id if supported - like ois
876 def cis (self, server):
877 if self.server_supports_call_id_arg (server):
878 return [ unique_call_id ]
882 ######################################## miscell utilities
883 def get_rspec_file(self, rspec):
884 if (os.path.isabs(rspec)):
887 file = os.path.join(self.options.sfi_dir, rspec)
888 if (os.path.isfile(file)):
891 self.logger.critical("No such rspec file {}".format(rspec))
894 def get_record_file(self, record):
895 if (os.path.isabs(record)):
898 file = os.path.join(self.options.sfi_dir, record)
899 if (os.path.isfile(file)):
902 self.logger.critical("No such registry record file {}".format(record))
906 # helper function to analyze raw output
907 # for main : return 0 if everything is fine, something else otherwise (mostly 1 for now)
908 def success (self, raw):
909 return_value=ReturnValue (raw)
910 output=ReturnValue.get_output(return_value)
911 # means everything is fine
914 # something went wrong
915 print 'ERROR:',output
918 #==========================================================================
919 # Following functions implement the commands
921 # Registry-related commands
922 #==========================================================================
924 @declare_command("","")
925 def config (self, options, args):
926 "Display contents of current config"
927 print "# From configuration file {}".format(self.config_file)
928 flags=[ ('sfi', [ ('registry','reg_url'),
929 ('auth','authority'),
935 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
937 for (section, tuples) in flags:
938 print "[{}]".format(section)
940 for external_name, internal_name in tuples:
941 print "{:<20} = {}".format(external_name, getattr(self, internal_name))
944 for external_name, internal_name in tuples:
945 varname = "{}_{}".format(section.upper(), external_name.upper())
946 value = getattr(self.config_instance,varname)
947 print "{:<20} = {}".format(external_name, value)
948 # xxx should analyze result
951 @declare_command("","")
952 def version(self, options, args):
954 display an SFA server version (GetVersion)
955 or version information about sfi itself
957 if options.version_local:
958 version=version_core()
960 if options.registry_interface:
961 server=self.registry()
963 server = self.sliceapi()
964 result = server.GetVersion()
965 version = ReturnValue.get_value(result)
967 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
969 pprinter = PrettyPrinter(indent=4)
970 pprinter.pprint(version)
971 # xxx should analyze result
974 @declare_command("authority","")
975 def list(self, options, args):
977 list entries in named authority registry (List)
984 if options.recursive:
985 opts['recursive'] = options.recursive
987 if options.show_credential:
988 show_credentials(self.my_credential_string)
990 list = self.registry().List(hrn, self.my_credential_string, options)
992 raise Exception, "Not enough parameters for the 'list' command"
994 # filter on person, slice, site, node, etc.
995 # This really should be in the self.filter_records funct def comment...
996 list = filter_records(options.type, list)
997 terminal_render (list, options)
999 save_records_to_file(options.file, list, options.fileformat)
1000 # xxx should analyze result
1003 @declare_command("name","")
1004 def show(self, options, args):
1006 show details about named registry record (Resolve)
1012 # explicitly require Resolve to run in details mode
1014 if not options.no_details: resolve_options['details']=True
1015 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, resolve_options)
1016 record_dicts = filter_records(options.type, record_dicts)
1017 if not record_dicts:
1018 self.logger.error("No record of type {}".format(options.type))
1020 # user has required to focus on some keys
1022 def project (record):
1024 for key in options.keys:
1025 try: projected[key]=record[key]
1028 record_dicts = [ project (record) for record in record_dicts ]
1029 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
1030 for record in records:
1031 if (options.format == "text"): record.dump(sort=True)
1032 else: print record.save_as_xml()
1034 save_records_to_file(options.file, record_dicts, options.fileformat)
1035 # xxx should analyze result
1038 # this historically was named 'add', it is now 'register' with an alias for legacy
1039 @declare_command("[xml-filename]","",['add'])
1040 def register(self, options, args):
1041 """create new record in registry (Register)
1042 from command line options (recommended)
1043 old-school method involving an xml file still supported"""
1045 auth_cred = self.my_authority_credential_string()
1046 if options.show_credential:
1047 show_credentials(auth_cred)
1054 record_filepath = args[0]
1055 rec_file = self.get_record_file(record_filepath)
1056 record_dict.update(load_record_from_file(rec_file).record_to_dict())
1058 print "Cannot load record file {}".format(record_filepath)
1061 record_dict.update(load_record_from_opts(options).record_to_dict())
1062 # we should have a type by now
1063 if 'type' not in record_dict :
1066 # this is still planetlab dependent.. as plc will whine without that
1067 # also, it's only for adding
1068 if record_dict['type'] == 'user':
1069 if not 'first_name' in record_dict:
1070 record_dict['first_name'] = record_dict['hrn']
1071 if 'last_name' not in record_dict:
1072 record_dict['last_name'] = record_dict['hrn']
1073 register = self.registry().Register(record_dict, auth_cred)
1074 # xxx looks like the result here is not ReturnValue-compatible
1075 #return self.success (register)
1076 # xxx should analyze result
1079 @declare_command("[xml-filename]","")
1080 def update(self, options, args):
1081 """update record into registry (Update)
1082 from command line options (recommended)
1083 old-school method involving an xml file still supported"""
1086 record_filepath = args[0]
1087 rec_file = self.get_record_file(record_filepath)
1088 record_dict.update(load_record_from_file(rec_file).record_to_dict())
1090 record_dict.update(load_record_from_opts(options).record_to_dict())
1091 # at the very least we need 'type' here
1092 if 'type' not in record_dict or record_dict['type'] is None:
1096 # don't translate into an object, as this would possibly distort
1097 # user-provided data; e.g. add an 'email' field to Users
1098 if record_dict['type'] in ['user']:
1099 if record_dict['hrn'] == self.user:
1100 cred = self.my_credential_string
1102 cred = self.my_authority_credential_string()
1103 elif record_dict['type'] in ['slice']:
1105 cred = self.slice_credential_string(record_dict['hrn'])
1106 except ServerException, e:
1107 # XXX smbaker -- once we have better error return codes, update this
1108 # to do something better than a string compare
1109 if "Permission error" in e.args[0]:
1110 cred = self.my_authority_credential_string()
1113 elif record_dict['type'] in ['authority']:
1114 cred = self.my_authority_credential_string()
1115 elif record_dict['type'] in ['node']:
1116 cred = self.my_authority_credential_string()
1118 raise Exception("unknown record type {}".format(record_dict['type']))
1119 if options.show_credential:
1120 show_credentials(cred)
1121 update = self.registry().Update(record_dict, cred)
1122 # xxx looks like the result here is not ReturnValue-compatible
1123 #return self.success(update)
1124 # xxx should analyze result
1127 @declare_command("hrn","")
1128 def remove(self, options, args):
1129 "remove registry record by name (Remove)"
1130 auth_cred = self.my_authority_credential_string()
1138 if options.show_credential:
1139 show_credentials(auth_cred)
1140 remove = self.registry().Remove(hrn, auth_cred, type)
1141 # xxx looks like the result here is not ReturnValue-compatible
1142 #return self.success (remove)
1143 # xxx should analyze result
1146 # ==================================================================
1147 # Slice-related commands
1148 # ==================================================================
1150 # show rspec for named slice
1151 @declare_command("","",['discover'])
1152 def resources(self, options, args):
1154 discover available resources (ListResources)
1156 server = self.sliceapi()
1159 creds = [self.my_credential]
1160 if options.delegate:
1161 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1162 if options.show_credential:
1163 show_credentials(creds)
1165 # no need to check if server accepts the options argument since the options has
1166 # been a required argument since v1 API
1168 # always send call_id to v2 servers
1169 api_options ['call_id'] = unique_call_id()
1170 # ask for cached value if available
1171 api_options ['cached'] = True
1173 api_options['info'] = options.info
1174 if options.list_leases:
1175 api_options['list_leases'] = options.list_leases
1177 if options.current == True:
1178 api_options['cached'] = False
1180 api_options['cached'] = True
1181 if options.rspec_version:
1182 version_manager = VersionManager()
1183 server_version = self.get_cached_server_version(server)
1184 if 'sfa' in server_version:
1185 # just request the version the client wants
1186 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1188 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1190 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1191 list_resources = server.ListResources (creds, api_options)
1192 value = ReturnValue.get_value(list_resources)
1193 if self.options.raw:
1194 save_raw_to_file(list_resources, self.options.raw, self.options.rawformat, self.options.rawbanner)
1195 if options.file is not None:
1196 save_rspec_to_file(value, options.file)
1197 if (self.options.raw is None) and (options.file is None):
1198 display_rspec(value, options.format)
1199 return self.success(list_resources)
1201 @declare_command("slice_hrn","")
1202 def describe(self, options, args):
1204 shows currently allocated/provisioned resources
1205 of the named slice or set of slivers (Describe)
1207 server = self.sliceapi()
1210 creds = [self.slice_credential(args[0])]
1211 if options.delegate:
1212 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1213 if options.show_credential:
1214 show_credentials(creds)
1216 api_options = {'call_id': unique_call_id(),
1218 'info': options.info,
1219 'list_leases': options.list_leases,
1220 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1223 api_options['info'] = options.info
1225 if options.rspec_version:
1226 version_manager = VersionManager()
1227 server_version = self.get_cached_server_version(server)
1228 if 'sfa' in server_version:
1229 # just request the version the client wants
1230 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1232 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1233 urn = Xrn(args[0], type='slice').get_urn()
1234 remove_none_fields(api_options)
1235 describe = server.Describe([urn], creds, api_options)
1236 value = ReturnValue.get_value(describe)
1237 if self.options.raw:
1238 save_raw_to_file(describe, self.options.raw, self.options.rawformat, self.options.rawbanner)
1239 if options.file is not None:
1240 save_rspec_to_file(value['geni_rspec'], options.file)
1241 if (self.options.raw is None) and (options.file is None):
1242 display_rspec(value['geni_rspec'], options.format)
1243 return self.success (describe)
1245 @declare_command("slice_hrn [<sliver_urn>...]","")
1246 def delete(self, options, args):
1248 de-allocate and de-provision all or named slivers of the named slice (Delete)
1250 server = self.sliceapi()
1254 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1257 # we have sliver urns
1258 sliver_urns = args[1:]
1260 # we provision all the slivers of the slice
1261 sliver_urns = [slice_urn]
1264 slice_cred = self.slice_credential(slice_hrn)
1265 creds = [slice_cred]
1267 # options and call_id when supported
1269 api_options ['call_id'] = unique_call_id()
1270 if options.show_credential:
1271 show_credentials(creds)
1272 delete = server.Delete(sliver_urns, creds, *self.ois(server, api_options ) )
1273 value = ReturnValue.get_value(delete)
1274 if self.options.raw:
1275 save_raw_to_file(delete, self.options.raw, self.options.rawformat, self.options.rawbanner)
1278 return self.success (delete)
1280 @declare_command("slice_hrn rspec","")
1281 def allocate(self, options, args):
1283 allocate resources to the named slice (Allocate)
1285 server = self.sliceapi()
1286 server_version = self.get_cached_server_version(server)
1291 rspec_file = self.get_rspec_file(args[1])
1293 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1296 creds = [self.slice_credential(slice_hrn)]
1298 delegated_cred = None
1299 if server_version.get('interface') == 'slicemgr':
1300 # delegate our cred to the slice manager
1301 # do not delegate cred to slicemgr...not working at the moment
1303 #if server_version.get('hrn'):
1304 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1305 #elif server_version.get('urn'):
1306 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1308 if options.show_credential:
1309 show_credentials(creds)
1313 api_options ['call_id'] = unique_call_id()
1317 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1318 remove_none_fields(slice_records[0])
1319 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1320 slice_record = slice_records[0]
1321 user_hrns = slice_record['reg-researchers']
1322 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1323 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1324 sfa_users = sfa_users_arg(user_records, slice_record)
1325 geni_users = pg_users_arg(user_records)
1327 api_options['sfa_users'] = sfa_users
1328 api_options['geni_users'] = geni_users
1330 with open(rspec_file) as rspec:
1331 rspec_xml = rspec.read()
1332 allocate = server.Allocate(slice_urn, creds, rspec_xml, api_options)
1333 value = ReturnValue.get_value(allocate)
1334 if self.options.raw:
1335 save_raw_to_file(allocate, self.options.raw, self.options.rawformat, self.options.rawbanner)
1336 if options.file is not None:
1337 save_rspec_to_file (value['geni_rspec'], options.file)
1338 if (self.options.raw is None) and (options.file is None):
1340 return self.success(allocate)
1342 @declare_command("slice_hrn [<sliver_urn>...]","")
1343 def provision(self, options, args):
1345 provision all or named already allocated slivers of the named slice (Provision)
1347 server = self.sliceapi()
1348 server_version = self.get_cached_server_version(server)
1350 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1352 # we have sliver urns
1353 sliver_urns = args[1:]
1355 # we provision all the slivers of the slice
1356 sliver_urns = [slice_urn]
1359 creds = [self.slice_credential(slice_hrn)]
1360 delegated_cred = None
1361 if server_version.get('interface') == 'slicemgr':
1362 # delegate our cred to the slice manager
1363 # do not delegate cred to slicemgr...not working at the moment
1365 #if server_version.get('hrn'):
1366 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1367 #elif server_version.get('urn'):
1368 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1370 if options.show_credential:
1371 show_credentials(creds)
1374 api_options ['call_id'] = unique_call_id()
1376 # set the requtested rspec version
1377 version_manager = VersionManager()
1378 rspec_version = version_manager._get_version('geni', '3').to_dict()
1379 api_options['geni_rspec_version'] = rspec_version
1382 # need to pass along user keys to the aggregate.
1384 # { urn: urn:publicid:IDN+emulab.net+user+alice
1385 # keys: [<ssh key A>, <ssh key B>]
1388 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1389 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1390 slice_record = slice_records[0]
1391 user_hrns = slice_record['reg-researchers']
1392 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1393 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1394 users = pg_users_arg(user_records)
1396 api_options['geni_users'] = users
1397 provision = server.Provision(sliver_urns, creds, api_options)
1398 value = ReturnValue.get_value(provision)
1399 if self.options.raw:
1400 save_raw_to_file(provision, self.options.raw, self.options.rawformat, self.options.rawbanner)
1401 if options.file is not None:
1402 save_rspec_to_file (value['geni_rspec'], options.file)
1403 if (self.options.raw is None) and (options.file is None):
1405 return self.success(provision)
1407 @declare_command("slice_hrn","")
1408 def status(self, options, args):
1410 retrieve the status of the slivers belonging to the named slice (Status)
1412 server = self.sliceapi()
1416 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1419 slice_cred = self.slice_credential(slice_hrn)
1420 creds = [slice_cred]
1422 # options and call_id when supported
1424 api_options['call_id']=unique_call_id()
1425 if options.show_credential:
1426 show_credentials(creds)
1427 status = server.Status([slice_urn], creds, *self.ois(server,api_options))
1428 value = ReturnValue.get_value(status)
1429 if self.options.raw:
1430 save_raw_to_file(status, self.options.raw, self.options.rawformat, self.options.rawbanner)
1433 return self.success (status)
1435 @declare_command("slice_hrn [<sliver_urn>...] action","")
1436 def action(self, options, args):
1438 Perform the named operational action on all or named slivers of the named slice
1440 server = self.sliceapi()
1444 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1446 # we have sliver urns
1447 sliver_urns = args[1:-1]
1449 # we provision all the slivers of the slice
1450 sliver_urns = [slice_urn]
1453 slice_cred = self.slice_credential(args[0])
1454 creds = [slice_cred]
1455 if options.delegate:
1456 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1457 creds.append(delegated_cred)
1459 perform_action = server.PerformOperationalAction(sliver_urns, creds, action , api_options)
1460 value = ReturnValue.get_value(perform_action)
1461 if self.options.raw:
1462 save_raw_to_file(perform_action, self.options.raw, self.options.rawformat, self.options.rawbanner)
1465 return self.success (perform_action)
1467 @declare_command("slice_hrn [<sliver_urn>...] time",
1468 "\n".join(["sfi renew onelab.ple.heartbeat 2015-04-31",
1469 "sfi renew onelab.ple.heartbeat 2015-04-31T14:00:00Z",
1470 "sfi renew onelab.ple.heartbeat +5d",
1471 "sfi renew onelab.ple.heartbeat +3w",
1472 "sfi renew onelab.ple.heartbeat +2m",]))
1473 def renew(self, options, args):
1477 server = self.sliceapi()
1482 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1485 # we have sliver urns
1486 sliver_urns = args[1:-1]
1488 # we provision all the slivers of the slice
1489 sliver_urns = [slice_urn]
1490 input_time = args[-1]
1492 # time: don't try to be smart on the time format, server-side will
1494 slice_cred = self.slice_credential(args[0])
1495 creds = [slice_cred]
1496 # options and call_id when supported
1498 api_options['call_id']=unique_call_id()
1500 api_options['geni_extend_alap']=True
1501 if options.show_credential:
1502 show_credentials(creds)
1503 renew = server.Renew(sliver_urns, creds, input_time, *self.ois(server,api_options))
1504 value = ReturnValue.get_value(renew)
1505 if self.options.raw:
1506 save_raw_to_file(renew, self.options.raw, self.options.rawformat, self.options.rawbanner)
1509 return self.success(renew)
1511 @declare_command("slice_hrn","")
1512 def shutdown(self, options, args):
1514 shutdown named slice (Shutdown)
1516 server = self.sliceapi()
1519 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1521 slice_cred = self.slice_credential(slice_hrn)
1522 creds = [slice_cred]
1523 shutdown = server.Shutdown(slice_urn, creds)
1524 value = ReturnValue.get_value(shutdown)
1525 if self.options.raw:
1526 save_raw_to_file(shutdown, self.options.raw, self.options.rawformat, self.options.rawbanner)
1529 return self.success (shutdown)
1531 @declare_command("[name]","")
1532 def gid(self, options, args):
1534 Create a GID (CreateGid)
1539 target_hrn = args[0]
1540 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1541 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1543 filename = options.file
1545 filename = os.sep.join([self.options.sfi_dir, '{}.gid'.format(target_hrn)])
1546 self.logger.info("writing {} gid to {}".format(target_hrn, filename))
1547 GID(string=gid).save_to_file(filename)
1548 # xxx should analyze result
1551 ####################
1552 @declare_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1554 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1555 the set of credentials in the scope for this call would be
1556 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1558 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1560 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1561 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1562 because of the two -s options
1565 def delegate (self, options, args):
1567 (locally) create delegate credential for use by given hrn
1568 make sure to check for 'sfi myslice' instead if you plan
1575 # support for several delegations in the same call
1576 # so first we gather the things to do
1578 for slice_hrn in options.delegate_slices:
1579 message = "{}.slice".format(slice_hrn)
1580 original = self.slice_credential_string(slice_hrn)
1581 tuples.append ( (message, original,) )
1582 if options.delegate_pi:
1583 my_authority=self.authority
1584 message = "{}.pi".format(my_authority)
1585 original = self.my_authority_credential_string()
1586 tuples.append ( (message, original,) )
1587 for auth_hrn in options.delegate_auths:
1588 message = "{}.auth".format(auth_hrn)
1589 original = self.authority_credential_string(auth_hrn)
1590 tuples.append ( (message, original, ) )
1591 # if nothing was specified at all at this point, let's assume -u
1592 if not tuples: options.delegate_user=True
1594 if options.delegate_user:
1595 message = "{}.user".format(self.user)
1596 original = self.my_credential_string
1597 tuples.append ( (message, original, ) )
1599 # default type for beneficial is user unless -A
1600 if options.delegate_to_authority: to_type='authority'
1601 else: to_type='user'
1603 # let's now handle all this
1604 # it's all in the filenaming scheme
1605 for (message,original) in tuples:
1606 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1607 delegated_credential = Credential (string=delegated_string)
1608 filename = os.path.join(self.options.sfi_dir,
1609 "{}_for_{}.{}.cred".format(message, to_hrn, to_type))
1610 delegated_credential.save_to_file(filename, save_parents=True)
1611 self.logger.info("delegated credential for {} to {} and wrote to {}"
1612 .format(message, to_hrn, filename))
1614 ####################
1615 @declare_command("","""$ less +/myslice sfi_config
1617 backend = http://manifold.pl.sophia.inria.fr:7080
1618 # the HRN that myslice uses, so that we are delegating to
1619 delegate = ple.upmc.slicebrowser
1620 # platform - this is a myslice concept
1622 # username - as of this writing (May 2013) a simple login name
1626 will first collect the slices that you are part of, then make sure
1627 all your credentials are up-to-date (read: refresh expired ones)
1628 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1629 and upload them all on myslice backend, using 'platform' and 'user'.
1630 A password will be prompted for the upload part.
1632 $ sfi -v myslice -- or sfi -vv myslice
1633 same but with more and more verbosity
1635 $ sfi m -b http://mymanifold.foo.com:7080/
1636 is synonym to sfi myslice as no other command starts with an 'm'
1637 and uses a custom backend for this one call
1640 def myslice (self, options, args):
1642 """ This helper is for refreshing your credentials at myslice; it will
1643 * compute all the slices that you currently have credentials on
1644 * refresh all your credentials (you as a user and pi, your slices)
1645 * upload them to the manifold backend server
1646 for last phase, sfi_config is read to look for the [myslice] section,
1647 and namely the 'backend', 'delegate' and 'user' settings"""
1653 # enable info by default
1654 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1655 ### the rough sketch goes like this
1656 # (0) produce a p12 file
1657 self.client_bootstrap.my_pkcs12()
1659 # (a) rain check for sufficient config in sfi_config
1661 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1662 for key in myslice_keys:
1664 # oct 2013 - I'm finding myself juggling with config files
1665 # so a couple of command-line options can now override config
1666 if hasattr(options,key) and getattr(options,key) is not None:
1667 value=getattr(options,key)
1669 full_key="MYSLICE_" + key.upper()
1670 value=getattr(self.config_instance,full_key,None)
1672 myslice_dict[key]=value
1674 print "Unsufficient config, missing key {} in [myslice] section of sfi_config"\
1676 if len(myslice_dict) != len(myslice_keys):
1679 # (b) figure whether we are PI for the authority where we belong
1680 self.logger.info("Resolving our own id {}".format(self.user))
1681 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1682 if len(my_records) != 1:
1683 print "Cannot Resolve {} -- exiting".format(self.user)
1685 my_record = my_records[0]
1686 my_auths_all = my_record['reg-pi-authorities']
1687 self.logger.info("Found {} authorities that we are PI for".format(len(my_auths_all)))
1688 self.logger.debug("They are {}".format(my_auths_all))
1690 my_auths = my_auths_all
1691 if options.delegate_auths:
1692 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1693 self.logger.debug("Restricted to user-provided auths {}".format(my_auths))
1695 # (c) get the set of slices that we are in
1696 my_slices_all=my_record['reg-slices']
1697 self.logger.info("Found {} slices that we are member of".format(len(my_slices_all)))
1698 self.logger.debug("They are: {}".format(my_slices_all))
1700 my_slices = my_slices_all
1701 # if user provided slices, deal only with these - if they are found
1702 if options.delegate_slices:
1703 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1704 self.logger.debug("Restricted to user-provided slices: {}".format(my_slices))
1706 # (d) make sure we have *valid* credentials for all these
1708 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1709 for auth_hrn in my_auths:
1710 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1711 for slice_hrn in my_slices:
1712 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1714 # (e) check for the delegated version of these
1715 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1716 # switch to myslice using an authority instead of a user
1717 delegatee_type='user'
1718 delegatee_hrn=myslice_dict['delegate']
1719 hrn_delegated_credentials = []
1720 for (hrn, htype, credential) in hrn_credentials:
1721 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1722 # save these so user can monitor what she's uploaded
1723 filename = os.path.join ( self.options.sfi_dir,
1724 "{}.{}_for_{}.{}.cred"\
1725 .format(hrn, htype, delegatee_hrn, delegatee_type))
1726 with file(filename,'w') as f:
1727 f.write(delegated_credential)
1728 self.logger.debug("(Over)wrote {}".format(filename))
1729 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1731 # (f) and finally upload them to manifold server
1732 # xxx todo add an option so the password can be set on the command line
1733 # (but *NOT* in the config file) so other apps can leverage this
1734 self.logger.info("Uploading on backend at {}".format(myslice_dict['backend']))
1735 uploader = ManifoldUploader (logger=self.logger,
1736 url=myslice_dict['backend'],
1737 platform=myslice_dict['platform'],
1738 username=myslice_dict['username'],
1739 password=options.password)
1740 uploader.prompt_all()
1741 (count_all,count_success)=(0,0)
1742 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1744 inspect=Credential(string=delegated_credential)
1745 expire_datetime=inspect.get_expiration()
1746 message="{} ({}) [exp:{}]".format(hrn, htype, expire_datetime)
1747 if uploader.upload(delegated_credential,message=message):
1750 self.logger.info("Successfully uploaded {}/{} credentials"
1751 .format(count_success, count_all))
1753 # at first I thought we would want to save these,
1754 # like 'sfi delegate does' but on second thought
1755 # it is probably not helpful as people would not
1756 # need to run 'sfi delegate' at all anymore
1757 if count_success != count_all: sys.exit(1)
1758 # xxx should analyze result
1761 @declare_command("cred","")
1762 def trusted(self, options, args):
1764 return the trusted certs at this interface (get_trusted_certs)
1766 if options.registry_interface:
1767 server=self.registry()
1769 server = self.sliceapi()
1770 cred = self.my_authority_credential_string()
1771 trusted_certs = server.get_trusted_certs(cred)
1772 if not options.registry_interface:
1773 trusted_certs = ReturnValue.get_value(trusted_certs)
1775 for trusted_cert in trusted_certs:
1776 print "\n===========================================================\n"
1777 gid = GID(string=trusted_cert)
1779 cert = Certificate(string=trusted_cert)
1780 self.logger.debug('Sfi.trusted -> {}'.format(cert.get_subject()))
1781 print "Certificate:\n{}\n\n".format(trusted_cert)
1782 # xxx should analyze result