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 varname = "{}_{}".format(section.upper(), name.upper())
945 value = getattr(self.config_instance,varname)
946 print "{:-20} = {}".format(name, value)
947 # xxx should analyze result
950 @declare_command("","")
951 def version(self, options, args):
953 display an SFA server version (GetVersion)
954 or version information about sfi itself
956 if options.version_local:
957 version=version_core()
959 if options.registry_interface:
960 server=self.registry()
962 server = self.sliceapi()
963 result = server.GetVersion()
964 version = ReturnValue.get_value(result)
966 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
968 pprinter = PrettyPrinter(indent=4)
969 pprinter.pprint(version)
970 # xxx should analyze result
973 @declare_command("authority","")
974 def list(self, options, args):
976 list entries in named authority registry (List)
983 if options.recursive:
984 opts['recursive'] = options.recursive
986 if options.show_credential:
987 show_credentials(self.my_credential_string)
989 list = self.registry().List(hrn, self.my_credential_string, options)
991 raise Exception, "Not enough parameters for the 'list' command"
993 # filter on person, slice, site, node, etc.
994 # This really should be in the self.filter_records funct def comment...
995 list = filter_records(options.type, list)
996 terminal_render (list, options)
998 save_records_to_file(options.file, list, options.fileformat)
999 # xxx should analyze result
1002 @declare_command("name","")
1003 def show(self, options, args):
1005 show details about named registry record (Resolve)
1011 # explicitly require Resolve to run in details mode
1013 if not options.no_details: resolve_options['details']=True
1014 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, resolve_options)
1015 record_dicts = filter_records(options.type, record_dicts)
1016 if not record_dicts:
1017 self.logger.error("No record of type {}".format(options.type))
1019 # user has required to focus on some keys
1021 def project (record):
1023 for key in options.keys:
1024 try: projected[key]=record[key]
1027 record_dicts = [ project (record) for record in record_dicts ]
1028 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
1029 for record in records:
1030 if (options.format == "text"): record.dump(sort=True)
1031 else: print record.save_as_xml()
1033 save_records_to_file(options.file, record_dicts, options.fileformat)
1034 # xxx should analyze result
1037 # this historically was named 'add', it is now 'register' with an alias for legacy
1038 @declare_command("[xml-filename]","",['add'])
1039 def register(self, options, args):
1040 """create new record in registry (Register)
1041 from command line options (recommended)
1042 old-school method involving an xml file still supported"""
1044 auth_cred = self.my_authority_credential_string()
1045 if options.show_credential:
1046 show_credentials(auth_cred)
1053 record_filepath = args[0]
1054 rec_file = self.get_record_file(record_filepath)
1055 record_dict.update(load_record_from_file(rec_file).record_to_dict())
1057 print "Cannot load record file {}".format(record_filepath)
1060 record_dict.update(load_record_from_opts(options).record_to_dict())
1061 # we should have a type by now
1062 if 'type' not in record_dict :
1065 # this is still planetlab dependent.. as plc will whine without that
1066 # also, it's only for adding
1067 if record_dict['type'] == 'user':
1068 if not 'first_name' in record_dict:
1069 record_dict['first_name'] = record_dict['hrn']
1070 if 'last_name' not in record_dict:
1071 record_dict['last_name'] = record_dict['hrn']
1072 register = self.registry().Register(record_dict, auth_cred)
1073 # xxx looks like the result here is not ReturnValue-compatible
1074 #return self.success (register)
1075 # xxx should analyze result
1078 @declare_command("[xml-filename]","")
1079 def update(self, options, args):
1080 """update record into registry (Update)
1081 from command line options (recommended)
1082 old-school method involving an xml file still supported"""
1085 record_filepath = args[0]
1086 rec_file = self.get_record_file(record_filepath)
1087 record_dict.update(load_record_from_file(rec_file).record_to_dict())
1089 record_dict.update(load_record_from_opts(options).record_to_dict())
1090 # at the very least we need 'type' here
1091 if 'type' not in record_dict or record_dict['type'] is None:
1095 # don't translate into an object, as this would possibly distort
1096 # user-provided data; e.g. add an 'email' field to Users
1097 if record_dict['type'] in ['user']:
1098 if record_dict['hrn'] == self.user:
1099 cred = self.my_credential_string
1101 cred = self.my_authority_credential_string()
1102 elif record_dict['type'] in ['slice']:
1104 cred = self.slice_credential_string(record_dict['hrn'])
1105 except ServerException, e:
1106 # XXX smbaker -- once we have better error return codes, update this
1107 # to do something better than a string compare
1108 if "Permission error" in e.args[0]:
1109 cred = self.my_authority_credential_string()
1112 elif record_dict['type'] in ['authority']:
1113 cred = self.my_authority_credential_string()
1114 elif record_dict['type'] in ['node']:
1115 cred = self.my_authority_credential_string()
1117 raise Exception("unknown record type {}".format(record_dict['type']))
1118 if options.show_credential:
1119 show_credentials(cred)
1120 update = self.registry().Update(record_dict, cred)
1121 # xxx looks like the result here is not ReturnValue-compatible
1122 #return self.success(update)
1123 # xxx should analyze result
1126 @declare_command("hrn","")
1127 def remove(self, options, args):
1128 "remove registry record by name (Remove)"
1129 auth_cred = self.my_authority_credential_string()
1137 if options.show_credential:
1138 show_credentials(auth_cred)
1139 remove = self.registry().Remove(hrn, auth_cred, type)
1140 # xxx looks like the result here is not ReturnValue-compatible
1141 #return self.success (remove)
1142 # xxx should analyze result
1145 # ==================================================================
1146 # Slice-related commands
1147 # ==================================================================
1149 # show rspec for named slice
1150 @declare_command("","",['discover'])
1151 def resources(self, options, args):
1153 discover available resources (ListResources)
1155 server = self.sliceapi()
1158 creds = [self.my_credential]
1159 if options.delegate:
1160 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1161 if options.show_credential:
1162 show_credentials(creds)
1164 # no need to check if server accepts the options argument since the options has
1165 # been a required argument since v1 API
1167 # always send call_id to v2 servers
1168 api_options ['call_id'] = unique_call_id()
1169 # ask for cached value if available
1170 api_options ['cached'] = True
1172 api_options['info'] = options.info
1173 if options.list_leases:
1174 api_options['list_leases'] = options.list_leases
1176 if options.current == True:
1177 api_options['cached'] = False
1179 api_options['cached'] = True
1180 if options.rspec_version:
1181 version_manager = VersionManager()
1182 server_version = self.get_cached_server_version(server)
1183 if 'sfa' in server_version:
1184 # just request the version the client wants
1185 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1187 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1189 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1190 list_resources = server.ListResources (creds, api_options)
1191 value = ReturnValue.get_value(list_resources)
1192 if self.options.raw:
1193 save_raw_to_file(list_resources, self.options.raw, self.options.rawformat, self.options.rawbanner)
1194 if options.file is not None:
1195 save_rspec_to_file(value, options.file)
1196 if (self.options.raw is None) and (options.file is None):
1197 display_rspec(value, options.format)
1198 return self.success(list_resources)
1200 @declare_command("slice_hrn","")
1201 def describe(self, options, args):
1203 shows currently allocated/provisioned resources
1204 of the named slice or set of slivers (Describe)
1206 server = self.sliceapi()
1209 creds = [self.slice_credential(args[0])]
1210 if options.delegate:
1211 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1212 if options.show_credential:
1213 show_credentials(creds)
1215 api_options = {'call_id': unique_call_id(),
1217 'info': options.info,
1218 'list_leases': options.list_leases,
1219 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1222 api_options['info'] = options.info
1224 if options.rspec_version:
1225 version_manager = VersionManager()
1226 server_version = self.get_cached_server_version(server)
1227 if 'sfa' in server_version:
1228 # just request the version the client wants
1229 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1231 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1232 urn = Xrn(args[0], type='slice').get_urn()
1233 remove_none_fields(api_options)
1234 describe = server.Describe([urn], creds, api_options)
1235 value = ReturnValue.get_value(describe)
1236 if self.options.raw:
1237 save_raw_to_file(describe, self.options.raw, self.options.rawformat, self.options.rawbanner)
1238 if options.file is not None:
1239 save_rspec_to_file(value['geni_rspec'], options.file)
1240 if (self.options.raw is None) and (options.file is None):
1241 display_rspec(value['geni_rspec'], options.format)
1242 return self.success (describe)
1244 @declare_command("slice_hrn [<sliver_urn>...]","")
1245 def delete(self, options, args):
1247 de-allocate and de-provision all or named slivers of the named slice (Delete)
1249 server = self.sliceapi()
1253 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1256 # we have sliver urns
1257 sliver_urns = args[1:]
1259 # we provision all the slivers of the slice
1260 sliver_urns = [slice_urn]
1263 slice_cred = self.slice_credential(slice_hrn)
1264 creds = [slice_cred]
1266 # options and call_id when supported
1268 api_options ['call_id'] = unique_call_id()
1269 if options.show_credential:
1270 show_credentials(creds)
1271 delete = server.Delete(sliver_urns, creds, *self.ois(server, api_options ) )
1272 value = ReturnValue.get_value(delete)
1273 if self.options.raw:
1274 save_raw_to_file(delete, self.options.raw, self.options.rawformat, self.options.rawbanner)
1277 return self.success (delete)
1279 @declare_command("slice_hrn rspec","")
1280 def allocate(self, options, args):
1282 allocate resources to the named slice (Allocate)
1284 server = self.sliceapi()
1285 server_version = self.get_cached_server_version(server)
1290 rspec_file = self.get_rspec_file(args[1])
1292 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1295 creds = [self.slice_credential(slice_hrn)]
1297 delegated_cred = None
1298 if server_version.get('interface') == 'slicemgr':
1299 # delegate our cred to the slice manager
1300 # do not delegate cred to slicemgr...not working at the moment
1302 #if server_version.get('hrn'):
1303 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1304 #elif server_version.get('urn'):
1305 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1307 if options.show_credential:
1308 show_credentials(creds)
1312 api_options ['call_id'] = unique_call_id()
1316 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1317 remove_none_fields(slice_records[0])
1318 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1319 slice_record = slice_records[0]
1320 user_hrns = slice_record['reg-researchers']
1321 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1322 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1323 sfa_users = sfa_users_arg(user_records, slice_record)
1324 geni_users = pg_users_arg(user_records)
1326 api_options['sfa_users'] = sfa_users
1327 api_options['geni_users'] = geni_users
1329 with open(rspec_file) as rspec:
1330 rspec_xml = rspec.read()
1331 allocate = server.Allocate(slice_urn, creds, rspec_xml, api_options)
1332 value = ReturnValue.get_value(allocate)
1333 if self.options.raw:
1334 save_raw_to_file(allocate, self.options.raw, self.options.rawformat, self.options.rawbanner)
1335 if options.file is not None:
1336 save_rspec_to_file (value['geni_rspec'], options.file)
1337 if (self.options.raw is None) and (options.file is None):
1339 return self.success(allocate)
1341 @declare_command("slice_hrn [<sliver_urn>...]","")
1342 def provision(self, options, args):
1344 provision all or named already allocated slivers of the named slice (Provision)
1346 server = self.sliceapi()
1347 server_version = self.get_cached_server_version(server)
1349 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1351 # we have sliver urns
1352 sliver_urns = args[1:]
1354 # we provision all the slivers of the slice
1355 sliver_urns = [slice_urn]
1358 creds = [self.slice_credential(slice_hrn)]
1359 delegated_cred = None
1360 if server_version.get('interface') == 'slicemgr':
1361 # delegate our cred to the slice manager
1362 # do not delegate cred to slicemgr...not working at the moment
1364 #if server_version.get('hrn'):
1365 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1366 #elif server_version.get('urn'):
1367 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1369 if options.show_credential:
1370 show_credentials(creds)
1373 api_options ['call_id'] = unique_call_id()
1375 # set the requtested rspec version
1376 version_manager = VersionManager()
1377 rspec_version = version_manager._get_version('geni', '3').to_dict()
1378 api_options['geni_rspec_version'] = rspec_version
1381 # need to pass along user keys to the aggregate.
1383 # { urn: urn:publicid:IDN+emulab.net+user+alice
1384 # keys: [<ssh key A>, <ssh key B>]
1387 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1388 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1389 slice_record = slice_records[0]
1390 user_hrns = slice_record['reg-researchers']
1391 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1392 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1393 users = pg_users_arg(user_records)
1395 api_options['geni_users'] = users
1396 provision = server.Provision(sliver_urns, creds, api_options)
1397 value = ReturnValue.get_value(provision)
1398 if self.options.raw:
1399 save_raw_to_file(provision, self.options.raw, self.options.rawformat, self.options.rawbanner)
1400 if options.file is not None:
1401 save_rspec_to_file (value['geni_rspec'], options.file)
1402 if (self.options.raw is None) and (options.file is None):
1404 return self.success(provision)
1406 @declare_command("slice_hrn","")
1407 def status(self, options, args):
1409 retrieve the status of the slivers belonging to the named slice (Status)
1411 server = self.sliceapi()
1415 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1418 slice_cred = self.slice_credential(slice_hrn)
1419 creds = [slice_cred]
1421 # options and call_id when supported
1423 api_options['call_id']=unique_call_id()
1424 if options.show_credential:
1425 show_credentials(creds)
1426 status = server.Status([slice_urn], creds, *self.ois(server,api_options))
1427 value = ReturnValue.get_value(status)
1428 if self.options.raw:
1429 save_raw_to_file(status, self.options.raw, self.options.rawformat, self.options.rawbanner)
1432 return self.success (status)
1434 @declare_command("slice_hrn [<sliver_urn>...] action","")
1435 def action(self, options, args):
1437 Perform the named operational action on all or named slivers of the named slice
1439 server = self.sliceapi()
1443 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1445 # we have sliver urns
1446 sliver_urns = args[1:-1]
1448 # we provision all the slivers of the slice
1449 sliver_urns = [slice_urn]
1452 slice_cred = self.slice_credential(args[0])
1453 creds = [slice_cred]
1454 if options.delegate:
1455 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1456 creds.append(delegated_cred)
1458 perform_action = server.PerformOperationalAction(sliver_urns, creds, action , api_options)
1459 value = ReturnValue.get_value(perform_action)
1460 if self.options.raw:
1461 save_raw_to_file(perform_action, self.options.raw, self.options.rawformat, self.options.rawbanner)
1464 return self.success (perform_action)
1466 @declare_command("slice_hrn [<sliver_urn>...] time",
1467 "\n".join(["sfi renew onelab.ple.heartbeat 2015-04-31",
1468 "sfi renew onelab.ple.heartbeat 2015-04-31T14:00:00Z",
1469 "sfi renew onelab.ple.heartbeat +5d",
1470 "sfi renew onelab.ple.heartbeat +3w",
1471 "sfi renew onelab.ple.heartbeat +2m",]))
1472 def renew(self, options, args):
1476 server = self.sliceapi()
1481 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1484 # we have sliver urns
1485 sliver_urns = args[1:-1]
1487 # we provision all the slivers of the slice
1488 sliver_urns = [slice_urn]
1489 input_time = args[-1]
1491 # time: don't try to be smart on the time format, server-side will
1493 slice_cred = self.slice_credential(args[0])
1494 creds = [slice_cred]
1495 # options and call_id when supported
1497 api_options['call_id']=unique_call_id()
1499 api_options['geni_extend_alap']=True
1500 if options.show_credential:
1501 show_credentials(creds)
1502 renew = server.Renew(sliver_urns, creds, input_time, *self.ois(server,api_options))
1503 value = ReturnValue.get_value(renew)
1504 if self.options.raw:
1505 save_raw_to_file(renew, self.options.raw, self.options.rawformat, self.options.rawbanner)
1508 return self.success(renew)
1510 @declare_command("slice_hrn","")
1511 def shutdown(self, options, args):
1513 shutdown named slice (Shutdown)
1515 server = self.sliceapi()
1518 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1520 slice_cred = self.slice_credential(slice_hrn)
1521 creds = [slice_cred]
1522 shutdown = server.Shutdown(slice_urn, creds)
1523 value = ReturnValue.get_value(shutdown)
1524 if self.options.raw:
1525 save_raw_to_file(shutdown, self.options.raw, self.options.rawformat, self.options.rawbanner)
1528 return self.success (shutdown)
1530 @declare_command("[name]","")
1531 def gid(self, options, args):
1533 Create a GID (CreateGid)
1538 target_hrn = args[0]
1539 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1540 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1542 filename = options.file
1544 filename = os.sep.join([self.options.sfi_dir, '{}.gid'.format(target_hrn)])
1545 self.logger.info("writing {} gid to {}".format(target_hrn, filename))
1546 GID(string=gid).save_to_file(filename)
1547 # xxx should analyze result
1550 ####################
1551 @declare_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1553 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1554 the set of credentials in the scope for this call would be
1555 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1557 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1559 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1560 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1561 because of the two -s options
1564 def delegate (self, options, args):
1566 (locally) create delegate credential for use by given hrn
1567 make sure to check for 'sfi myslice' instead if you plan
1574 # support for several delegations in the same call
1575 # so first we gather the things to do
1577 for slice_hrn in options.delegate_slices:
1578 message = "{}.slice".format(slice_hrn)
1579 original = self.slice_credential_string(slice_hrn)
1580 tuples.append ( (message, original,) )
1581 if options.delegate_pi:
1582 my_authority=self.authority
1583 message = "{}.pi".format(my_authority)
1584 original = self.my_authority_credential_string()
1585 tuples.append ( (message, original,) )
1586 for auth_hrn in options.delegate_auths:
1587 message = "{}.auth".format(auth_hrn)
1588 original = self.authority_credential_string(auth_hrn)
1589 tuples.append ( (message, original, ) )
1590 # if nothing was specified at all at this point, let's assume -u
1591 if not tuples: options.delegate_user=True
1593 if options.delegate_user:
1594 message = "{}.user".format(self.user)
1595 original = self.my_credential_string
1596 tuples.append ( (message, original, ) )
1598 # default type for beneficial is user unless -A
1599 if options.delegate_to_authority: to_type='authority'
1600 else: to_type='user'
1602 # let's now handle all this
1603 # it's all in the filenaming scheme
1604 for (message,original) in tuples:
1605 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1606 delegated_credential = Credential (string=delegated_string)
1607 filename = os.path.join(self.options.sfi_dir,
1608 "{}_for_{}.{}.cred".format(message, to_hrn, to_type))
1609 delegated_credential.save_to_file(filename, save_parents=True)
1610 self.logger.info("delegated credential for {} to {} and wrote to {}"
1611 .format(message, to_hrn, filename))
1613 ####################
1614 @declare_command("","""$ less +/myslice sfi_config
1616 backend = http://manifold.pl.sophia.inria.fr:7080
1617 # the HRN that myslice uses, so that we are delegating to
1618 delegate = ple.upmc.slicebrowser
1619 # platform - this is a myslice concept
1621 # username - as of this writing (May 2013) a simple login name
1625 will first collect the slices that you are part of, then make sure
1626 all your credentials are up-to-date (read: refresh expired ones)
1627 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1628 and upload them all on myslice backend, using 'platform' and 'user'.
1629 A password will be prompted for the upload part.
1631 $ sfi -v myslice -- or sfi -vv myslice
1632 same but with more and more verbosity
1634 $ sfi m -b http://mymanifold.foo.com:7080/
1635 is synonym to sfi myslice as no other command starts with an 'm'
1636 and uses a custom backend for this one call
1639 def myslice (self, options, args):
1641 """ This helper is for refreshing your credentials at myslice; it will
1642 * compute all the slices that you currently have credentials on
1643 * refresh all your credentials (you as a user and pi, your slices)
1644 * upload them to the manifold backend server
1645 for last phase, sfi_config is read to look for the [myslice] section,
1646 and namely the 'backend', 'delegate' and 'user' settings"""
1652 # enable info by default
1653 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1654 ### the rough sketch goes like this
1655 # (0) produce a p12 file
1656 self.client_bootstrap.my_pkcs12()
1658 # (a) rain check for sufficient config in sfi_config
1660 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1661 for key in myslice_keys:
1663 # oct 2013 - I'm finding myself juggling with config files
1664 # so a couple of command-line options can now override config
1665 if hasattr(options,key) and getattr(options,key) is not None:
1666 value=getattr(options,key)
1668 full_key="MYSLICE_" + key.upper()
1669 value=getattr(self.config_instance,full_key,None)
1671 myslice_dict[key]=value
1673 print "Unsufficient config, missing key {} in [myslice] section of sfi_config"\
1675 if len(myslice_dict) != len(myslice_keys):
1678 # (b) figure whether we are PI for the authority where we belong
1679 self.logger.info("Resolving our own id {}".format(self.user))
1680 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1681 if len(my_records) != 1:
1682 print "Cannot Resolve {} -- exiting".format(self.user)
1684 my_record = my_records[0]
1685 my_auths_all = my_record['reg-pi-authorities']
1686 self.logger.info("Found {} authorities that we are PI for".format(len(my_auths_all)))
1687 self.logger.debug("They are {}".format(my_auths_all))
1689 my_auths = my_auths_all
1690 if options.delegate_auths:
1691 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1692 self.logger.debug("Restricted to user-provided auths {}".format(my_auths))
1694 # (c) get the set of slices that we are in
1695 my_slices_all=my_record['reg-slices']
1696 self.logger.info("Found {} slices that we are member of".format(len(my_slices_all)))
1697 self.logger.debug("They are: {}".format(my_slices_all))
1699 my_slices = my_slices_all
1700 # if user provided slices, deal only with these - if they are found
1701 if options.delegate_slices:
1702 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1703 self.logger.debug("Restricted to user-provided slices: {}".format(my_slices))
1705 # (d) make sure we have *valid* credentials for all these
1707 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1708 for auth_hrn in my_auths:
1709 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1710 for slice_hrn in my_slices:
1711 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1713 # (e) check for the delegated version of these
1714 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1715 # switch to myslice using an authority instead of a user
1716 delegatee_type='user'
1717 delegatee_hrn=myslice_dict['delegate']
1718 hrn_delegated_credentials = []
1719 for (hrn, htype, credential) in hrn_credentials:
1720 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1721 # save these so user can monitor what she's uploaded
1722 filename = os.path.join ( self.options.sfi_dir,
1723 "{}.{}_for_{}.{}.cred"\
1724 .format(hrn, htype, delegatee_hrn, delegatee_type))
1725 with file(filename,'w') as f:
1726 f.write(delegated_credential)
1727 self.logger.debug("(Over)wrote {}".format(filename))
1728 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1730 # (f) and finally upload them to manifold server
1731 # xxx todo add an option so the password can be set on the command line
1732 # (but *NOT* in the config file) so other apps can leverage this
1733 self.logger.info("Uploading on backend at {}".format(myslice_dict['backend']))
1734 uploader = ManifoldUploader (logger=self.logger,
1735 url=myslice_dict['backend'],
1736 platform=myslice_dict['platform'],
1737 username=myslice_dict['username'],
1738 password=options.password)
1739 uploader.prompt_all()
1740 (count_all,count_success)=(0,0)
1741 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1743 inspect=Credential(string=delegated_credential)
1744 expire_datetime=inspect.get_expiration()
1745 message="{} ({}) [exp:{}]".format(hrn, htype, expire_datetime)
1746 if uploader.upload(delegated_credential,message=message):
1749 self.logger.info("Successfully uploaded {}/{} credentials"
1750 .format(count_success, count_all))
1752 # at first I thought we would want to save these,
1753 # like 'sfi delegate does' but on second thought
1754 # it is probably not helpful as people would not
1755 # need to run 'sfi delegate' at all anymore
1756 if count_success != count_all: sys.exit(1)
1757 # xxx should analyze result
1760 @declare_command("cred","")
1761 def trusted(self, options, args):
1763 return the trusted certs at this interface (get_trusted_certs)
1765 if options.registry_interface:
1766 server=self.registry()
1768 server = self.sliceapi()
1769 cred = self.my_authority_credential_string()
1770 trusted_certs = server.get_trusted_certs(cred)
1771 if not options.registry_interface:
1772 trusted_certs = ReturnValue.get_value(trusted_certs)
1774 for trusted_cert in trusted_certs:
1775 print "\n===========================================================\n"
1776 gid = GID(string=trusted_cert)
1778 cert = Certificate(string=trusted_cert)
1779 self.logger.debug('Sfi.trusted -> {}'.format(cert.get_subject()))
1780 print "Certificate:\n{}\n\n".format(trusted_cert)
1781 # xxx should analyze result