2 # sfi.py - basic SFA command-line client
3 # this module is also used in sfascan
6 from __future__ import print_function
19 from lxml import etree
20 from StringIO import StringIO
21 from optparse import OptionParser
22 from pprint import PrettyPrinter
23 from tempfile import mkstemp
25 from sfa.trust.certificate import Keypair, Certificate
26 from sfa.trust.gid import GID
27 from sfa.trust.credential import Credential
28 from sfa.trust.sfaticket import SfaTicket
30 from sfa.util.faults import SfaInvalidArgument
31 from sfa.util.sfalogging import sfi_logger
32 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
33 from sfa.util.config import Config
34 from sfa.util.version import version_core
35 from sfa.util.cache import Cache
36 from sfa.util.printable import printable
38 from sfa.storage.record import Record
40 from sfa.rspecs.rspec import RSpec
41 from sfa.rspecs.rspec_converter import RSpecConverter
42 from sfa.rspecs.version_manager import VersionManager
44 from sfa.client.sfaclientlib import SfaClientBootstrap
45 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
46 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
47 from sfa.client.return_value import ReturnValue
48 from sfa.client.candidates import Candidates
49 from sfa.client.manifolduploader import ManifoldUploader
52 DEFAULT_RSPEC_VERSION = "GENI 3"
54 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
55 terminal_render, filter_records
58 def display_rspec(rspec, format='rspec'):
60 tree = etree.parse(StringIO(rspec))
62 result = root.xpath("./network/site/node/hostname/text()")
63 elif format in ['ip']:
64 # The IP address is not yet part of the new RSpec
65 # so this doesn't do anything yet.
66 tree = etree.parse(StringIO(rspec))
68 result = root.xpath("./network/site/node/ipv4/text()")
75 def display_list(results):
76 for result in results:
79 def display_records(recordList, dump=False):
80 ''' Print all fields in the record'''
81 for record in recordList:
82 display_record(record, dump)
84 def display_record(record, dump=False):
86 record.dump(sort=True)
88 info = record.getdict()
89 print("{} ({})".format(info['hrn'], info['type']))
93 def filter_records(type, records):
95 for record in records:
96 if (record['type'] == type) or (type == "all"):
97 filtered_records.append(record)
98 return filtered_records
101 def credential_printable (cred):
102 credential = Credential(cred=cred)
104 result += credential.pretty_cred()
106 rights = credential.get_privileges()
107 result += "type={}\n".format(credential.type)
108 result += "version={}\n".format(credential.version)
109 result += "rights={}\n".format(rights)
112 def show_credentials (cred_s):
113 if not isinstance (cred_s, list): cred_s = [cred_s]
115 print("Using Credential {}".format(credential_printable(cred)))
117 ########## save methods
120 def save_raw_to_file(var, filename, format='text', banner=None):
122 _save_raw_to_file(var, sys.stdout, format, banner)
124 with open(filename, w) as fileobj:
125 _save_raw_to_file(var, fileobj, format, banner)
126 print("(Over)wrote {}".format(filename))
128 def _save_raw_to_file(var, f, format, banner):
130 if banner: f.write(banner+"\n")
131 f.write("{}".format(var))
132 if banner: f.write('\n'+banner+"\n")
133 elif format == "pickled":
134 f.write(pickle.dumps(var))
135 elif format == "json":
136 f.write(json.dumps(var)) # python 2.6
138 # this should never happen
139 print("unknown output format", format)
142 def save_rspec_to_file(rspec, filename):
143 if not filename.endswith(".rspec"):
144 filename = filename + ".rspec"
145 with open(filename, 'w') as f:
146 f.write("{}".format(rspec))
147 print("(Over)wrote {}".format(filename))
149 def save_record_to_file(filename, record_dict):
150 record = Record(dict=record_dict)
151 xml = record.save_as_xml()
152 with codecs.open(filename, encoding='utf-8', mode="w") as f:
154 print("(Over)wrote {}".format(filename))
156 def save_records_to_file(filename, record_dicts, format="xml"):
158 for index, record_dict in enumerate(record_dicts):
159 save_record_to_file(filename + "." + str(index), record_dict)
160 elif format == "xmllist":
161 with open(filename, "w") as f:
162 f.write("<recordlist>\n")
163 for record_dict in record_dicts:
164 record_obj = Record(dict=record_dict)
165 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
166 f.write("</recordlist>\n")
167 print("(Over)wrote {}".format(filename))
169 elif format == "hrnlist":
170 with open(filename, "w") as f:
171 for record_dict in record_dicts:
172 record_obj = Record(dict=record_dict)
173 f.write(record_obj.hrn + "\n")
174 print("(Over)wrote {}".format(filename))
177 # this should never happen
178 print("unknown output format", format)
180 # minimally check a key argument
181 def check_ssh_key (key):
182 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
183 return re.match(good_ssh_key, key, re.IGNORECASE)
186 def normalize_type (type):
187 if type.startswith('au'):
189 elif type.startswith('us'):
191 elif type.startswith('sl'):
193 elif type.startswith('no'):
195 elif type.startswith('ag'):
197 elif type.startswith('al'):
200 print('unknown type {} - should start with one of au|us|sl|no|ag|al'.format(type))
203 def load_record_from_opts(options):
205 if hasattr(options, 'xrn') and options.xrn:
206 if hasattr(options, 'type') and options.type:
207 xrn = Xrn(options.xrn, options.type)
209 xrn = Xrn(options.xrn)
210 record_dict['urn'] = xrn.get_urn()
211 record_dict['hrn'] = xrn.get_hrn()
212 record_dict['type'] = xrn.get_type()
213 if hasattr(options, 'key') and options.key:
215 pubkey = open(options.key, 'r').read()
218 if not check_ssh_key (pubkey):
219 raise SfaInvalidArgument(name='key', msg="Could not find file, or wrong key format")
220 record_dict['reg-keys'] = [pubkey]
221 if hasattr(options, 'slices') and options.slices:
222 record_dict['slices'] = options.slices
223 if hasattr(options, 'reg_researchers') and options.reg_researchers is not None:
224 record_dict['reg-researchers'] = options.reg_researchers
225 if hasattr(options, 'email') and options.email:
226 record_dict['email'] = options.email
227 # authorities can have a name for standalone deployment
228 if hasattr(options, 'name') and options.name:
229 record_dict['name'] = options.name
230 if hasattr(options, 'reg_pis') and options.reg_pis:
231 record_dict['reg-pis'] = options.reg_pis
233 # handle extra settings
234 record_dict.update(options.extras)
236 return Record(dict=record_dict)
238 def load_record_from_file(filename):
239 with codecs.open(filename, encoding="utf-8", mode="r") as f:
241 return Record(xml=xml_str)
244 def unique_call_id(): return uuid.uuid4().urn
246 ########## a simple model for maintaing 3 doc attributes per command (instead of just one)
247 # essentially for the methods that implement a subcommand like sfi list
248 # we need to keep track of
249 # (*) doc a few lines that tell what it does, still located in __doc__
250 # (*) args_string a simple one-liner that describes mandatory arguments
251 # (*) example well, one or several releant examples
253 # since __doc__ only accounts for one, we use this simple mechanism below
254 # however we keep doc in place for easier migration
256 from functools import wraps
258 # we use a list as well as a dict so we can keep track of the order
262 def declare_command (args_string, example, aliases=None):
264 name=getattr(m, '__name__')
265 doc=getattr(m, '__doc__', "-- missing doc --")
266 doc=doc.strip(" \t\n")
267 commands_list.append(name)
268 # last item is 'canonical' name, so we can know which commands are aliases
269 command_tuple=(doc, args_string, example, name)
270 commands_dict[name]=command_tuple
271 if aliases is not None:
272 for alias in aliases:
273 commands_list.append(alias)
274 commands_dict[alias]=command_tuple
276 def new_method (*args, **kwds): return m(*args, **kwds)
281 def remove_none_fields (record):
282 none_fields=[ k for (k, v) in record.items() if v is None ]
283 for k in none_fields: del record[k]
289 # dirty hack to make this class usable from the outside
290 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
293 def default_sfi_dir ():
294 if os.path.isfile("./sfi_config"):
297 return os.path.expanduser("~/.sfi/")
299 # dummy to meet Sfi's expectations for its 'options' field
300 # i.e. s/t we can do setattr on
304 def __init__ (self, options=None):
305 if options is None: options=Sfi.DummyOptions()
306 for opt in Sfi.required_options:
307 if not hasattr(options, opt):
308 setattr(options, opt, None)
309 if not hasattr(options, 'sfi_dir'):
310 options.sfi_dir = Sfi.default_sfi_dir()
311 self.options = options
313 self.authority = None
314 self.logger = sfi_logger
315 self.logger.enable_console()
316 ### various auxiliary material that we keep at hand
318 # need to call this other than just 'config' as we have a command/method with that name
319 self.config_instance = None
320 self.config_file = None
321 self.client_bootstrap = None
323 ### suitable if no reasonable command has been provided
324 def print_commands_help (self, options):
325 verbose = getattr(options, 'verbose')
326 format3 = "%10s %-35s %s"
330 print(format3%("command", "cmd_args", "description"))
334 self.create_parser_global().print_help()
335 # preserve order from the code
336 for command in commands_list:
338 (doc, args_string, example, canonical) = commands_dict[command]
340 print("Cannot find info on command %s - skipped"%command)
344 if command==canonical:
345 doc = doc.replace("\n", "\n" + format3offset * ' ')
346 print(format3 % (command, args_string, doc))
348 self.create_parser_command(command).print_help()
350 print(format3 % (command, "<<alias for %s>>"%canonical, ""))
352 ### now if a known command was found we can be more verbose on that one
353 def print_help (self):
354 print("==================== Generic sfi usage")
355 self.sfi_parser.print_help()
356 (doc, _, example, canonical) = commands_dict[self.command]
357 if canonical != self.command:
358 print("\n==================== NOTE: {} is an alias for genuine {}"
359 .format(self.command, canonical))
360 self.command = canonical
361 print("\n==================== Purpose of {}".format(self.command))
363 print("\n==================== Specific usage for {}".format(self.command))
364 self.command_parser.print_help()
366 print("\n==================== {} example(s)".format(self.command))
369 def create_parser_global(self):
370 # Generate command line parser
371 parser = OptionParser(add_help_option=False,
372 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
373 description="Commands: {}".format(" ".join(commands_list)))
374 parser.add_option("-r", "--registry", dest="registry",
375 help="root registry", metavar="URL", default=None)
376 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
377 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
378 parser.add_option("-R", "--raw", dest="raw", default=None,
379 help="Save raw, unparsed server response to a file")
380 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
381 help="raw file format ([text]|pickled|json)", default="text",
382 choices=("text","pickled","json"))
383 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
384 help="text string to write before and after raw output")
385 parser.add_option("-d", "--dir", dest="sfi_dir",
386 help="config & working directory - default is %default",
387 metavar="PATH", default=Sfi.default_sfi_dir())
388 parser.add_option("-u", "--user", dest="user",
389 help="user name", metavar="HRN", default=None)
390 parser.add_option("-a", "--auth", dest="auth",
391 help="authority name", metavar="HRN", default=None)
392 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
393 help="verbose mode - cumulative")
394 parser.add_option("-D", "--debug",
395 action="store_true", dest="debug", default=False,
396 help="Debug (xml-rpc) protocol messages")
397 # would it make sense to use ~/.ssh/id_rsa as a default here ?
398 parser.add_option("-k", "--private-key",
399 action="store", dest="user_private_key", default=None,
400 help="point to the private key file to use if not yet installed in sfi_dir")
401 parser.add_option("-t", "--timeout", dest="timeout", default=None,
402 help="Amout of time to wait before timing out the request")
403 parser.add_option("-h", "--help",
404 action="store_true", dest="help", default=False,
405 help="one page summary on commands & exit")
406 parser.disable_interspersed_args()
411 def create_parser_command(self, command):
412 if command not in commands_dict:
413 msg="Invalid command\n"
415 msg += ','.join(commands_list)
416 self.logger.critical(msg)
419 # retrieve args_string
420 (_, args_string, __, canonical) = commands_dict[command]
422 parser = OptionParser(add_help_option=False,
423 usage="sfi [sfi_options] {} [cmd_options] {}"\
424 .format(command, args_string))
425 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
426 help="Summary of one command usage")
428 if canonical in ("config"):
429 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
430 help='how myslice config variables as well')
432 if canonical in ("version"):
433 parser.add_option("-l","--local",
434 action="store_true", dest="version_local", default=False,
435 help="display version of the local client")
437 if canonical in ("version", "trusted"):
438 parser.add_option("-R","--registry_interface",
439 action="store_true", dest="registry_interface", default=False,
440 help="target the registry interface instead of slice interface")
442 if canonical in ("register", "update"):
443 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
444 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type (2 first chars is enough)', default=None)
445 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
446 parser.add_option('-n', '--name', dest='name', default="", help="name (optional for authorities)")
447 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
449 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
450 default='', type="str", action='callback', callback=optparse_listvalue_callback)
451 parser.add_option('-r', '--researchers', dest='reg_researchers', metavar='<researchers>',
452 help='Set/replace slice researchers - use -r none to reset', default=None, type="str", action='callback',
453 callback=optparse_listvalue_callback)
454 parser.add_option('-p', '--pis', dest='reg_pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
455 default='', type="str", action='callback', callback=optparse_listvalue_callback)
456 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
457 action="callback", callback=optparse_dictvalue_callback, nargs=1,
458 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
460 # user specifies remote aggregate/sm/component
461 if canonical in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
462 "action", "shutdown", "renew", "status"):
463 parser.add_option("-d", "--delegate", dest="delegate", default=None,
465 help="Include a credential delegated to the user's root"+\
466 "authority in set of credentials for this call")
468 # show_credential option
469 if canonical in ("list","resources", "describe", "provision", "allocate", "register","update","remove","delete","status","renew"):
470 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
471 help="show credential(s) used in human-readable form")
472 if canonical in ("renew"):
473 parser.add_option("-l","--as-long-as-possible",dest='alap',action='store_true',default=False,
474 help="renew as long as possible")
475 # registy filter option
476 if canonical in ("list", "show", "remove"):
477 parser.add_option("-t", "--type", dest="type", metavar="<type>",
479 help="type filter - 2 first chars is enough ([all]|user|slice|authority|node|aggregate)")
480 if canonical in ("show"):
481 parser.add_option("-k","--key",dest="keys",action="append",default=[],
482 help="specify specific keys to be displayed from record")
483 parser.add_option("-n","--no-details",dest="no_details",action="store_true",default=False,
484 help="call Resolve without the 'details' option")
485 if canonical in ("resources", "describe"):
487 parser.add_option("-r", "--rspec-version", dest="rspec_version", default=DEFAULT_RSPEC_VERSION,
488 help="schema type and version of resulting RSpec (default:{})".format(DEFAULT_RSPEC_VERSION))
489 # disable/enable cached rspecs
490 parser.add_option("-c", "--current", dest="current", default=False,
492 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
494 parser.add_option("-f", "--format", dest="format", type="choice",
495 help="display format ([xml]|dns|ip)", default="xml",
496 choices=("xml", "dns", "ip"))
497 #panos: a new option to define the type of information about resources a user is interested in
498 parser.add_option("-i", "--info", dest="info",
499 help="optional component information", default=None)
500 # a new option to retrieve or not reservation-oriented RSpecs (leases)
501 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
502 help="Retrieve or not reservation-oriented RSpecs ([resources]|leases|all)",
503 choices=("all", "resources", "leases"), default="resources")
506 if canonical in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
507 parser.add_option("-o", "--output", dest="file",
508 help="output XML to file", metavar="FILE", default=None)
510 if canonical in ("show", "list"):
511 parser.add_option("-f", "--format", dest="format", type="choice",
512 help="display format ([text]|xml)", default="text",
513 choices=("text", "xml"))
515 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
516 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
517 choices=("xml", "xmllist", "hrnlist"))
518 if canonical == 'list':
519 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
520 help="list all child records", default=False)
521 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
522 help="gives details, like user keys", default=False)
523 if canonical in ("delegate"):
524 parser.add_option("-u", "--user",
525 action="store_true", dest="delegate_user", default=False,
526 help="delegate your own credentials; default if no other option is provided")
527 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
528 metavar="slice_hrn", help="delegate cred. for slice HRN")
529 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
530 metavar='auth_hrn', help="delegate cred for auth HRN")
531 # this primarily is a shorthand for -A my_hrn^
532 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
533 help="delegate your PI credentials, so s.t. like -A your_hrn^")
534 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
535 help="""by default the mandatory argument is expected to be a user,
536 use this if you mean an authority instead""")
538 if canonical in ("myslice"):
539 parser.add_option("-p","--password",dest='password',action='store',default=None,
540 help="specify mainfold password on the command line")
541 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
542 metavar="slice_hrn", help="delegate cred. for slice HRN")
543 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
544 metavar='auth_hrn', help="delegate PI cred for auth HRN")
545 parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
546 parser.add_option('-b', '--backend', dest='backend', help="Override 'backend' from the config file")
552 # Main: parse arguments and dispatch to command
554 def dispatch(self, command, command_options, command_args):
555 (doc, args_string, example, canonical) = commands_dict[command]
556 method=getattr(self, canonical, None)
558 print("sfi: unknown command {}".format(command))
559 raise SystemExit("Unknown command {}".format(command))
560 for arg in command_args:
561 if 'help' in arg or arg == '-h':
564 return method(command_options, command_args)
567 self.sfi_parser = self.create_parser_global()
568 (options, args) = self.sfi_parser.parse_args()
570 self.print_commands_help(options)
572 self.options = options
574 self.logger.setLevelFromOptVerbose(self.options.verbose)
577 self.logger.critical("No command given. Use -h for help.")
578 self.print_commands_help(options)
581 # complete / find unique match with command set
582 command_candidates = Candidates (commands_list)
584 command = command_candidates.only_match(input)
586 self.print_commands_help(options)
588 # second pass options parsing
589 self.command = command
590 self.command_parser = self.create_parser_command(command)
591 (command_options, command_args) = self.command_parser.parse_args(args[1:])
592 if command_options.help:
595 self.command_options = command_options
597 # allow incoming types on 2 characters only
598 if hasattr(command_options, 'type'):
599 command_options.type = normalize_type(command_options.type)
600 if not command_options.type:
605 self.logger.debug("Command={}".format(self.command))
608 retcod = self.dispatch(command, command_options, command_args)
612 self.logger.log_exc ("sfi command {} failed".format(command))
617 def read_config(self):
618 config_file = os.path.join(self.options.sfi_dir, "sfi_config")
619 shell_config_file = os.path.join(self.options.sfi_dir, "sfi_config.sh")
621 if Config.is_ini(config_file):
622 config = Config (config_file)
624 # try upgrading from shell config format
625 fp, fn = mkstemp(suffix='sfi_config', text=True)
627 # we need to preload the sections we want parsed
628 # from the shell config
629 config.add_section('sfi')
630 # sface users should be able to use this same file to configure their stuff
631 config.add_section('sface')
632 # manifold users should be able to specify the details
633 # of their backend server here for 'sfi myslice'
634 config.add_section('myslice')
635 config.load(config_file)
637 shutil.move(config_file, shell_config_file)
639 config.save(config_file)
642 self.logger.critical("Failed to read configuration file {}".format(config_file))
643 self.logger.info("Make sure to remove the export clauses and to add quotes")
644 if self.options.verbose == 0:
645 self.logger.info("Re-run with -v for more details")
647 self.logger.log_exc("Could not read config file {}".format(config_file))
650 self.config_instance = config
653 if (self.options.sm is not None):
654 self.sm_url = self.options.sm
655 elif hasattr(config, "SFI_SM"):
656 self.sm_url = config.SFI_SM
658 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in {}".format(config_file))
662 if (self.options.registry is not None):
663 self.reg_url = self.options.registry
664 elif hasattr(config, "SFI_REGISTRY"):
665 self.reg_url = config.SFI_REGISTRY
667 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in {}".format(config_file))
671 if (self.options.user is not None):
672 self.user = self.options.user
673 elif hasattr(config, "SFI_USER"):
674 self.user = config.SFI_USER
676 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in {}".format(config_file))
680 if (self.options.auth is not None):
681 self.authority = self.options.auth
682 elif hasattr(config, "SFI_AUTH"):
683 self.authority = config.SFI_AUTH
685 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in {}".format(config_file))
688 self.config_file = config_file
693 # Get various credential and spec files
695 # Establishes limiting conventions
696 # - conflates MAs and SAs
697 # - assumes last token in slice name is unique
699 # Bootstraps credentials
700 # - bootstrap user credential from self-signed certificate
701 # - bootstrap authority credential from user credential
702 # - bootstrap slice credential from user credential
705 # init self-signed cert, user credentials and gid
706 def bootstrap (self):
707 if self.options.verbose:
708 self.logger.info("Initializing SfaClientBootstrap with {}".format(self.reg_url))
709 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
711 # if -k is provided, use this to initialize private key
712 if self.options.user_private_key:
713 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
715 # trigger legacy compat code if needed
716 # the name has changed from just <leaf>.pkey to <hrn>.pkey
717 if not os.path.isfile(client_bootstrap.private_key_filename()):
718 self.logger.info ("private key not found, trying legacy name")
720 legacy_private_key = os.path.join (self.options.sfi_dir, "{}.pkey"
721 .format(Xrn.unescape(get_leaf(self.user))))
722 self.logger.debug("legacy_private_key={}"
723 .format(legacy_private_key))
724 client_bootstrap.init_private_key_if_missing (legacy_private_key)
725 self.logger.info("Copied private key from legacy location {}"
726 .format(legacy_private_key))
728 self.logger.log_exc("Can't find private key ")
732 client_bootstrap.bootstrap_my_gid()
733 # extract what's needed
734 self.private_key = client_bootstrap.private_key()
735 self.my_credential_string = client_bootstrap.my_credential_string ()
736 self.my_credential = {'geni_type': 'geni_sfa',
738 'geni_value': self.my_credential_string}
739 self.my_gid = client_bootstrap.my_gid ()
740 self.client_bootstrap = client_bootstrap
743 def my_authority_credential_string(self):
744 if not self.authority:
745 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
747 return self.client_bootstrap.authority_credential_string (self.authority)
749 def authority_credential_string(self, auth_hrn):
750 return self.client_bootstrap.authority_credential_string (auth_hrn)
752 def slice_credential_string(self, name):
753 return self.client_bootstrap.slice_credential_string (name)
755 def slice_credential(self, name):
756 return {'geni_type': 'geni_sfa',
758 'geni_value': self.slice_credential_string(name)}
760 # xxx should be supported by sfaclientbootstrap as well
761 def delegate_cred(self, object_cred, hrn, type='authority'):
762 # the gid and hrn of the object we are delegating
763 if isinstance(object_cred, str):
764 object_cred = Credential(string=object_cred)
765 object_gid = object_cred.get_gid_object()
766 object_hrn = object_gid.get_hrn()
768 if not object_cred.get_privileges().get_all_delegate():
769 self.logger.error("Object credential {} does not have delegate bit set"
773 # the delegating user's gid
774 caller_gidfile = self.my_gid()
776 # the gid of the user who will be delegated to
777 delegee_gid = self.client_bootstrap.gid(hrn, type)
778 delegee_hrn = delegee_gid.get_hrn()
779 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
780 return dcred.save_to_string(save_parents=True)
783 # Management of the servers
788 if not hasattr (self, 'registry_proxy'):
789 self.logger.info("Contacting Registry at: {}".format(self.reg_url))
790 self.registry_proxy \
791 = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
792 timeout=self.options.timeout, verbose=self.options.debug)
793 return self.registry_proxy
797 if not hasattr (self, 'sliceapi_proxy'):
798 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
799 if hasattr(self.command_options, 'component') and self.command_options.component:
800 # resolve the hrn at the registry
801 node_hrn = self.command_options.component
802 records = self.registry().Resolve(node_hrn, self.my_credential_string)
803 records = filter_records('node', records)
805 self.logger.warning("No such component:{}".format(opts.component))
807 cm_url = "http://{}:{}/".format(record['hostname'], CM_PORT)
808 self.sliceapi_proxy = SfaServerProxy(cm_url, self.private_key, self.my_gid)
810 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
811 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
812 self.sm_url = 'http://' + self.sm_url
813 self.logger.info("Contacting Slice Manager at: {}".format(self.sm_url))
814 self.sliceapi_proxy \
815 = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
816 timeout=self.options.timeout, verbose=self.options.debug)
817 return self.sliceapi_proxy
819 def get_cached_server_version(self, server):
820 # check local cache first
823 cache_file = os.path.join(self.options.sfi_dir, 'sfi_cache.dat')
824 cache_key = server.url + "-version"
826 cache = Cache(cache_file)
829 self.logger.info("Local cache not found at: {}".format(cache_file))
832 version = cache.get(cache_key)
835 result = server.GetVersion()
836 version = ReturnValue.get_value(result)
837 # cache version for 20 minutes
838 cache.add(cache_key, version, ttl=60*20)
839 self.logger.info("Updating cache file {}".format(cache_file))
840 cache.save_to_file(cache_file)
844 ### resurrect this temporarily so we can support V1 aggregates for a while
845 def server_supports_options_arg(self, server):
847 Returns true if server support the optional call_id arg, false otherwise.
849 server_version = self.get_cached_server_version(server)
851 # xxx need to rewrite this
852 if int(server_version.get('geni_api')) >= 2:
856 def server_supports_call_id_arg(self, server):
857 server_version = self.get_cached_server_version(server)
859 if 'sfa' in server_version and 'code_tag' in server_version:
860 code_tag = server_version['code_tag']
861 code_tag_parts = code_tag.split("-")
862 version_parts = code_tag_parts[0].split(".")
863 major, minor = version_parts[0], version_parts[1]
864 rev = code_tag_parts[1]
865 if int(major) == 1 and minor == 0 and build >= 22:
869 ### ois = options if supported
870 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
871 def ois (self, server, option_dict):
872 if self.server_supports_options_arg (server):
874 elif self.server_supports_call_id_arg (server):
875 return [ unique_call_id () ]
879 ### cis = call_id if supported - like ois
880 def cis (self, server):
881 if self.server_supports_call_id_arg (server):
882 return [ unique_call_id ]
886 ######################################## miscell utilities
887 def get_rspec_file(self, rspec):
888 if (os.path.isabs(rspec)):
891 file = os.path.join(self.options.sfi_dir, rspec)
892 if (os.path.isfile(file)):
895 self.logger.critical("No such rspec file {}".format(rspec))
898 def get_record_file(self, record):
899 if (os.path.isabs(record)):
902 file = os.path.join(self.options.sfi_dir, record)
903 if (os.path.isfile(file)):
906 self.logger.critical("No such registry record file {}".format(record))
910 # helper function to analyze raw output
911 # for main : return 0 if everything is fine, something else otherwise (mostly 1 for now)
912 def success (self, raw):
913 return_value = ReturnValue(raw)
914 output = ReturnValue.get_output(return_value)
915 # means everything is fine
918 # something went wrong
919 print('ERROR:', output)
922 #==========================================================================
923 # Following functions implement the commands
925 # Registry-related commands
926 #==========================================================================
928 @declare_command("", "")
929 def config (self, options, args):
931 Display contents of current config
937 print("# From configuration file {}".format(self.config_file))
938 flags = [ ('sfi', [ ('registry', 'reg_url'),
939 ('auth', 'authority'),
945 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
947 for (section, tuples) in flags:
948 print("[{}]".format(section))
950 for external_name, internal_name in tuples:
951 print("{:<20} = {}".format(external_name, getattr(self, internal_name)))
953 for external_name, internal_name in tuples:
954 varname = "{}_{}".format(section.upper(), external_name.upper())
955 value = getattr(self.config_instance, varname)
956 print("{:<20} = {}".format(external_name, value))
957 # xxx should analyze result
960 @declare_command("", "")
961 def version(self, options, args):
963 display an SFA server version (GetVersion)
964 or version information about sfi itself
970 if options.version_local:
971 version = version_core()
973 if options.registry_interface:
974 server = self.registry()
976 server = self.sliceapi()
977 result = server.GetVersion()
978 version = ReturnValue.get_value(result)
980 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
982 pprinter = PrettyPrinter(indent=4)
983 pprinter.pprint(version)
984 # xxx should analyze result
987 @declare_command("authority", "")
988 def list(self, options, args):
990 list entries in named authority registry (List)
998 if options.recursive:
999 opts['recursive'] = options.recursive
1001 if options.show_credential:
1002 show_credentials(self.my_credential_string)
1004 list = self.registry().List(hrn, self.my_credential_string, options)
1006 raise Exception("Not enough parameters for the 'list' command")
1008 # filter on person, slice, site, node, etc.
1009 # This really should be in the self.filter_records funct def comment...
1010 list = filter_records(options.type, list)
1011 terminal_render (list, options)
1013 save_records_to_file(options.file, list, options.fileformat)
1014 # xxx should analyze result
1017 @declare_command("name", "")
1018 def show(self, options, args):
1020 show details about named registry record (Resolve)
1027 # explicitly require Resolve to run in details mode
1028 resolve_options = {}
1029 if not options.no_details:
1030 resolve_options['details'] = True
1031 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, resolve_options)
1032 record_dicts = filter_records(options.type, record_dicts)
1033 if not record_dicts:
1034 self.logger.error("No record of type {}".format(options.type))
1036 # user has required to focus on some keys
1038 def project (record):
1040 for key in options.keys:
1041 try: projected[key] = record[key]
1044 record_dicts = [ project (record) for record in record_dicts ]
1045 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
1046 for record in records:
1047 if (options.format == "text"): record.dump(sort=True)
1048 else: print(record.save_as_xml())
1050 save_records_to_file(options.file, record_dicts, options.fileformat)
1051 # xxx should analyze result
1054 # this historically was named 'add', it is now 'register' with an alias for legacy
1055 @declare_command("[xml-filename]", "", ['add'])
1056 def register(self, options, args):
1058 create new record in registry (Register)
1059 from command line options (recommended)
1060 old-school method involving an xml file still supported
1066 auth_cred = self.my_authority_credential_string()
1067 if options.show_credential:
1068 show_credentials(auth_cred)
1072 record_filepath = args[0]
1073 rec_file = self.get_record_file(record_filepath)
1074 record_dict.update(load_record_from_file(rec_file).record_to_dict())
1076 print("Cannot load record file {}".format(record_filepath))
1079 record_dict.update(load_record_from_opts(options).record_to_dict())
1080 # we should have a type by now
1081 if 'type' not in record_dict :
1084 # this is still planetlab dependent.. as plc will whine without that
1085 # also, it's only for adding
1086 if record_dict['type'] == 'user':
1087 if not 'first_name' in record_dict:
1088 record_dict['first_name'] = record_dict['hrn']
1089 if 'last_name' not in record_dict:
1090 record_dict['last_name'] = record_dict['hrn']
1091 register = self.registry().Register(record_dict, auth_cred)
1092 # xxx looks like the result here is not ReturnValue-compatible
1093 #return self.success (register)
1094 # xxx should analyze result
1097 @declare_command("[xml-filename]", "")
1098 def update(self, options, args):
1100 update record into registry (Update)
1101 from command line options (recommended)
1102 old-school method involving an xml file still supported
1110 record_filepath = args[0]
1111 rec_file = self.get_record_file(record_filepath)
1112 record_dict.update(load_record_from_file(rec_file).record_to_dict())
1114 record_dict.update(load_record_from_opts(options).record_to_dict())
1115 # at the very least we need 'type' here
1116 if 'type' not in record_dict or record_dict['type'] is None:
1120 # don't translate into an object, as this would possibly distort
1121 # user-provided data; e.g. add an 'email' field to Users
1122 if record_dict['type'] in ['user']:
1123 if record_dict['hrn'] == self.user:
1124 cred = self.my_credential_string
1126 cred = self.my_authority_credential_string()
1127 elif record_dict['type'] in ['slice']:
1129 cred = self.slice_credential_string(record_dict['hrn'])
1130 except ServerException as e:
1131 # XXX smbaker -- once we have better error return codes, update this
1132 # to do something better than a string compare
1133 if "Permission error" in e.args[0]:
1134 cred = self.my_authority_credential_string()
1137 elif record_dict['type'] in ['authority']:
1138 cred = self.my_authority_credential_string()
1139 elif record_dict['type'] in ['node']:
1140 cred = self.my_authority_credential_string()
1142 raise Exception("unknown record type {}".format(record_dict['type']))
1143 if options.show_credential:
1144 show_credentials(cred)
1145 update = self.registry().Update(record_dict, cred)
1146 # xxx looks like the result here is not ReturnValue-compatible
1147 #return self.success(update)
1148 # xxx should analyze result
1151 @declare_command("hrn", "")
1152 def remove(self, options, args):
1154 remove registry record by name (Remove)
1156 auth_cred = self.my_authority_credential_string()
1165 if options.show_credential:
1166 show_credentials(auth_cred)
1167 remove = self.registry().Remove(hrn, auth_cred, type)
1168 # xxx looks like the result here is not ReturnValue-compatible
1169 #return self.success (remove)
1170 # xxx should analyze result
1173 # ==================================================================
1174 # Slice-related commands
1175 # ==================================================================
1177 # show rspec for named slice
1178 @declare_command("", "", ['discover'])
1179 def resources(self, options, args):
1181 discover available resources (ListResources)
1187 server = self.sliceapi()
1189 creds = [self.my_credential]
1190 if options.delegate:
1191 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1192 if options.show_credential:
1193 show_credentials(creds)
1195 # no need to check if server accepts the options argument since the options has
1196 # been a required argument since v1 API
1198 # always send call_id to v2 servers
1199 api_options ['call_id'] = unique_call_id()
1200 # ask for cached value if available
1201 api_options ['cached'] = True
1203 api_options['info'] = options.info
1204 if options.list_leases:
1205 api_options['list_leases'] = options.list_leases
1207 if options.current == True:
1208 api_options['cached'] = False
1210 api_options['cached'] = True
1211 if options.rspec_version:
1212 version_manager = VersionManager()
1213 server_version = self.get_cached_server_version(server)
1214 if 'sfa' in server_version:
1215 # just request the version the client wants
1216 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1218 api_options['geni_rspec_version'] = {'type': options.rspec_version}
1220 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1222 list_resources = server.ListResources (creds, api_options)
1223 value = ReturnValue.get_value(list_resources)
1224 if self.options.raw:
1225 save_raw_to_file(list_resources, self.options.raw, self.options.rawformat, self.options.rawbanner)
1226 if options.file is not None:
1227 save_rspec_to_file(value, options.file)
1228 if (self.options.raw is None) and (options.file is None):
1229 display_rspec(value, options.format)
1230 return self.success(list_resources)
1232 @declare_command("slice_hrn", "")
1233 def describe(self, options, args):
1235 shows currently allocated/provisioned resources
1236 of the named slice or set of slivers (Describe)
1242 server = self.sliceapi()
1244 creds = [self.slice_credential(args[0])]
1245 if options.delegate:
1246 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1247 if options.show_credential:
1248 show_credentials(creds)
1250 api_options = {'call_id': unique_call_id(),
1252 'info': options.info,
1253 'list_leases': options.list_leases,
1254 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1257 api_options['info'] = options.info
1259 if options.rspec_version:
1260 version_manager = VersionManager()
1261 server_version = self.get_cached_server_version(server)
1262 if 'sfa' in server_version:
1263 # just request the version the client wants
1264 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1266 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1267 urn = Xrn(args[0], type='slice').get_urn()
1268 remove_none_fields(api_options)
1269 describe = server.Describe([urn], creds, api_options)
1270 value = ReturnValue.get_value(describe)
1271 if self.options.raw:
1272 save_raw_to_file(describe, self.options.raw, self.options.rawformat, self.options.rawbanner)
1273 if options.file is not None:
1274 save_rspec_to_file(value['geni_rspec'], options.file)
1275 if (self.options.raw is None) and (options.file is None):
1276 display_rspec(value['geni_rspec'], options.format)
1277 return self.success (describe)
1279 @declare_command("slice_hrn [<sliver_urn>...]", "")
1280 def delete(self, options, args):
1282 de-allocate and de-provision all or named slivers of the named slice (Delete)
1288 server = self.sliceapi()
1291 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1294 # we have sliver urns
1295 sliver_urns = args[1:]
1297 # we provision all the slivers of the slice
1298 sliver_urns = [slice_urn]
1301 slice_cred = self.slice_credential(slice_hrn)
1302 creds = [slice_cred]
1304 # options and call_id when supported
1306 api_options ['call_id'] = unique_call_id()
1307 if options.show_credential:
1308 show_credentials(creds)
1309 delete = server.Delete(sliver_urns, creds, *self.ois(server, api_options ) )
1310 value = ReturnValue.get_value(delete)
1311 if self.options.raw:
1312 save_raw_to_file(delete, self.options.raw, self.options.rawformat, self.options.rawbanner)
1315 return self.success (delete)
1317 @declare_command("slice_hrn rspec", "")
1318 def allocate(self, options, args):
1320 allocate resources to the named slice (Allocate)
1326 server = self.sliceapi()
1327 server_version = self.get_cached_server_version(server)
1329 rspec_file = self.get_rspec_file(args[1])
1331 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1334 creds = [self.slice_credential(slice_hrn)]
1336 delegated_cred = None
1337 if server_version.get('interface') == 'slicemgr':
1338 # delegate our cred to the slice manager
1339 # do not delegate cred to slicemgr...not working at the moment
1341 #if server_version.get('hrn'):
1342 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1343 #elif server_version.get('urn'):
1344 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1346 if options.show_credential:
1347 show_credentials(creds)
1351 api_options ['call_id'] = unique_call_id()
1355 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1356 remove_none_fields(slice_records[0])
1357 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers'] != []:
1358 slice_record = slice_records[0]
1359 user_hrns = slice_record['reg-researchers']
1360 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1361 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1362 sfa_users = sfa_users_arg(user_records, slice_record)
1363 geni_users = pg_users_arg(user_records)
1365 api_options['sfa_users'] = sfa_users
1366 api_options['geni_users'] = geni_users
1368 with open(rspec_file) as rspec:
1369 rspec_xml = rspec.read()
1370 allocate = server.Allocate(slice_urn, creds, rspec_xml, api_options)
1371 value = ReturnValue.get_value(allocate)
1372 if self.options.raw:
1373 save_raw_to_file(allocate, self.options.raw, self.options.rawformat, self.options.rawbanner)
1374 if options.file is not None:
1375 save_rspec_to_file (value['geni_rspec'], options.file)
1376 if (self.options.raw is None) and (options.file is None):
1378 return self.success(allocate)
1380 @declare_command("slice_hrn [<sliver_urn>...]", "")
1381 def provision(self, options, args):
1383 provision all or named already allocated slivers of the named slice (Provision)
1389 server = self.sliceapi()
1390 server_version = self.get_cached_server_version(server)
1392 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1394 # we have sliver urns
1395 sliver_urns = args[1:]
1397 # we provision all the slivers of the slice
1398 sliver_urns = [slice_urn]
1401 creds = [self.slice_credential(slice_hrn)]
1402 delegated_cred = None
1403 if server_version.get('interface') == 'slicemgr':
1404 # delegate our cred to the slice manager
1405 # do not delegate cred to slicemgr...not working at the moment
1407 #if server_version.get('hrn'):
1408 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1409 #elif server_version.get('urn'):
1410 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1412 if options.show_credential:
1413 show_credentials(creds)
1416 api_options ['call_id'] = unique_call_id()
1418 # set the requtested rspec version
1419 version_manager = VersionManager()
1420 rspec_version = version_manager._get_version('geni', '3').to_dict()
1421 api_options['geni_rspec_version'] = rspec_version
1424 # need to pass along user keys to the aggregate.
1426 # { urn: urn:publicid:IDN+emulab.net+user+alice
1427 # keys: [<ssh key A>, <ssh key B>]
1430 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1431 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers'] != []:
1432 slice_record = slice_records[0]
1433 user_hrns = slice_record['reg-researchers']
1434 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1435 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1436 users = pg_users_arg(user_records)
1438 api_options['geni_users'] = users
1439 provision = server.Provision(sliver_urns, creds, api_options)
1440 value = ReturnValue.get_value(provision)
1441 if self.options.raw:
1442 save_raw_to_file(provision, self.options.raw, self.options.rawformat, self.options.rawbanner)
1443 if options.file is not None:
1444 save_rspec_to_file (value['geni_rspec'], options.file)
1445 if (self.options.raw is None) and (options.file is None):
1447 return self.success(provision)
1449 @declare_command("slice_hrn", "")
1450 def status(self, options, args):
1452 retrieve the status of the slivers belonging to the named slice (Status)
1458 server = self.sliceapi()
1461 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1464 slice_cred = self.slice_credential(slice_hrn)
1465 creds = [slice_cred]
1467 # options and call_id when supported
1469 api_options['call_id'] = unique_call_id()
1470 if options.show_credential:
1471 show_credentials(creds)
1472 status = server.Status([slice_urn], creds, *self.ois(server, api_options))
1473 value = ReturnValue.get_value(status)
1474 if self.options.raw:
1475 save_raw_to_file(status, self.options.raw, self.options.rawformat, self.options.rawbanner)
1478 return self.success (status)
1480 @declare_command("slice_hrn [<sliver_urn>...] action", "")
1481 def action(self, options, args):
1483 Perform the named operational action on all or named slivers of the named slice
1489 server = self.sliceapi()
1493 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1495 # we have sliver urns
1496 sliver_urns = args[1:-1]
1498 # we provision all the slivers of the slice
1499 sliver_urns = [slice_urn]
1502 slice_cred = self.slice_credential(args[0])
1503 creds = [slice_cred]
1504 if options.delegate:
1505 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1506 creds.append(delegated_cred)
1508 perform_action = server.PerformOperationalAction(sliver_urns, creds, action , api_options)
1509 value = ReturnValue.get_value(perform_action)
1510 if self.options.raw:
1511 save_raw_to_file(perform_action, self.options.raw, self.options.rawformat, self.options.rawbanner)
1514 return self.success (perform_action)
1516 @declare_command("slice_hrn [<sliver_urn>...] time",
1517 "\n".join(["sfi renew onelab.ple.heartbeat 2015-04-31",
1518 "sfi renew onelab.ple.heartbeat 2015-04-31T14:00:00Z",
1519 "sfi renew onelab.ple.heartbeat +5d",
1520 "sfi renew onelab.ple.heartbeat +3w",
1521 "sfi renew onelab.ple.heartbeat +2m",]))
1522 def renew(self, options, args):
1530 server = self.sliceapi()
1532 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1535 # we have sliver urns
1536 sliver_urns = args[1:-1]
1538 # we provision all the slivers of the slice
1539 sliver_urns = [slice_urn]
1540 input_time = args[-1]
1542 # time: don't try to be smart on the time format, server-side will
1544 slice_cred = self.slice_credential(args[0])
1545 creds = [slice_cred]
1546 # options and call_id when supported
1548 api_options['call_id'] = unique_call_id()
1550 api_options['geni_extend_alap'] = True
1551 if options.show_credential:
1552 show_credentials(creds)
1553 renew = server.Renew(sliver_urns, creds, input_time, *self.ois(server, api_options))
1554 value = ReturnValue.get_value(renew)
1555 if self.options.raw:
1556 save_raw_to_file(renew, self.options.raw, self.options.rawformat, self.options.rawbanner)
1559 return self.success(renew)
1561 @declare_command("slice_hrn", "")
1562 def shutdown(self, options, args):
1564 shutdown named slice (Shutdown)
1570 server = self.sliceapi()
1573 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1575 slice_cred = self.slice_credential(slice_hrn)
1576 creds = [slice_cred]
1577 shutdown = server.Shutdown(slice_urn, creds)
1578 value = ReturnValue.get_value(shutdown)
1579 if self.options.raw:
1580 save_raw_to_file(shutdown, self.options.raw, self.options.rawformat, self.options.rawbanner)
1583 return self.success (shutdown)
1585 @declare_command("[name]", "")
1586 def gid(self, options, args):
1588 Create a GID (CreateGid)
1594 target_hrn = args[0]
1595 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1596 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1598 filename = options.file
1600 filename = os.sep.join([self.options.sfi_dir, '{}.gid'.format(target_hrn)])
1601 self.logger.info("writing {} gid to {}".format(target_hrn, filename))
1602 GID(string=gid).save_to_file(filename)
1603 # xxx should analyze result
1606 ####################
1607 @declare_command("to_hrn", """$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1609 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1610 the set of credentials in the scope for this call would be
1611 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1613 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1615 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1616 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1617 because of the two -s options
1620 def delegate (self, options, args):
1622 (locally) create delegate credential for use by given hrn
1623 make sure to check for 'sfi myslice' instead if you plan
1631 # support for several delegations in the same call
1632 # so first we gather the things to do
1634 for slice_hrn in options.delegate_slices:
1635 message = "{}.slice".format(slice_hrn)
1636 original = self.slice_credential_string(slice_hrn)
1637 tuples.append ( (message, original,) )
1638 if options.delegate_pi:
1639 my_authority = self.authority
1640 message = "{}.pi".format(my_authority)
1641 original = self.my_authority_credential_string()
1642 tuples.append ( (message, original,) )
1643 for auth_hrn in options.delegate_auths:
1644 message = "{}.auth".format(auth_hrn)
1645 original = self.authority_credential_string(auth_hrn)
1646 tuples.append ( (message, original, ) )
1647 # if nothing was specified at all at this point, let's assume -u
1649 options.delegate_user = True
1651 if options.delegate_user:
1652 message = "{}.user".format(self.user)
1653 original = self.my_credential_string
1654 tuples.append ( (message, original, ) )
1656 # default type for beneficial is user unless -A
1657 to_type = 'authority' if options.delegate_to_authority else 'user'
1659 # let's now handle all this
1660 # it's all in the filenaming scheme
1661 for (message, original) in tuples:
1662 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1663 delegated_credential = Credential (string=delegated_string)
1664 filename = os.path.join(self.options.sfi_dir,
1665 "{}_for_{}.{}.cred".format(message, to_hrn, to_type))
1666 delegated_credential.save_to_file(filename, save_parents=True)
1667 self.logger.info("delegated credential for {} to {} and wrote to {}"
1668 .format(message, to_hrn, filename))
1670 ####################
1671 @declare_command("", """$ less +/myslice sfi_config
1673 backend = http://manifold.pl.sophia.inria.fr:7080
1674 # the HRN that myslice uses, so that we are delegating to
1675 delegate = ple.upmc.slicebrowser
1676 # platform - this is a myslice concept
1678 # username - as of this writing (May 2013) a simple login name
1682 will first collect the slices that you are part of, then make sure
1683 all your credentials are up-to-date (read: refresh expired ones)
1684 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1685 and upload them all on myslice backend, using 'platform' and 'user'.
1686 A password will be prompted for the upload part.
1688 $ sfi -v myslice -- or sfi -vv myslice
1689 same but with more and more verbosity
1691 $ sfi m -b http://mymanifold.foo.com:7080/
1692 is synonym to sfi myslice as no other command starts with an 'm'
1693 and uses a custom backend for this one call
1696 def myslice (self, options, args):
1698 """ This helper is for refreshing your credentials at myslice; it will
1699 * compute all the slices that you currently have credentials on
1700 * refresh all your credentials (you as a user and pi, your slices)
1701 * upload them to the manifold backend server
1702 for last phase, sfi_config is read to look for the [myslice] section,
1703 and namely the 'backend', 'delegate' and 'user' settings
1710 # enable info by default
1711 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1712 ### the rough sketch goes like this
1713 # (0) produce a p12 file
1714 self.client_bootstrap.my_pkcs12()
1716 # (a) rain check for sufficient config in sfi_config
1718 myslice_keys = [ 'backend', 'delegate', 'platform', 'username']
1719 for key in myslice_keys:
1721 # oct 2013 - I'm finding myself juggling with config files
1722 # so a couple of command-line options can now override config
1723 if hasattr(options, key) and getattr(options, key) is not None:
1724 value = getattr(options, key)
1726 full_key = "MYSLICE_" + key.upper()
1727 value = getattr(self.config_instance, full_key, None)
1729 myslice_dict[key] = value
1731 print("Unsufficient config, missing key {} in [myslice] section of sfi_config"
1733 if len(myslice_dict) != len(myslice_keys):
1736 # (b) figure whether we are PI for the authority where we belong
1737 self.logger.info("Resolving our own id {}".format(self.user))
1738 my_records = self.registry().Resolve(self.user, self.my_credential_string)
1739 if len(my_records) != 1:
1740 print("Cannot Resolve {} -- exiting".format(self.user))
1742 my_record = my_records[0]
1743 my_auths_all = my_record['reg-pi-authorities']
1744 self.logger.info("Found {} authorities that we are PI for".format(len(my_auths_all)))
1745 self.logger.debug("They are {}".format(my_auths_all))
1747 my_auths = my_auths_all
1748 if options.delegate_auths:
1749 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1750 self.logger.debug("Restricted to user-provided auths {}".format(my_auths))
1752 # (c) get the set of slices that we are in
1753 my_slices_all = my_record['reg-slices']
1754 self.logger.info("Found {} slices that we are member of".format(len(my_slices_all)))
1755 self.logger.debug("They are: {}".format(my_slices_all))
1757 my_slices = my_slices_all
1758 # if user provided slices, deal only with these - if they are found
1759 if options.delegate_slices:
1760 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1761 self.logger.debug("Restricted to user-provided slices: {}".format(my_slices))
1763 # (d) make sure we have *valid* credentials for all these
1764 hrn_credentials = []
1765 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1766 for auth_hrn in my_auths:
1767 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1768 for slice_hrn in my_slices:
1769 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1771 # (e) check for the delegated version of these
1772 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1773 # switch to myslice using an authority instead of a user
1774 delegatee_type = 'user'
1775 delegatee_hrn = myslice_dict['delegate']
1776 hrn_delegated_credentials = []
1777 for (hrn, htype, credential) in hrn_credentials:
1778 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1779 # save these so user can monitor what she's uploaded
1780 filename = os.path.join ( self.options.sfi_dir,
1781 "{}.{}_for_{}.{}.cred"\
1782 .format(hrn, htype, delegatee_hrn, delegatee_type))
1783 with file(filename, 'w') as f:
1784 f.write(delegated_credential)
1785 self.logger.debug("(Over)wrote {}".format(filename))
1786 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1788 # (f) and finally upload them to manifold server
1789 # xxx todo add an option so the password can be set on the command line
1790 # (but *NOT* in the config file) so other apps can leverage this
1791 self.logger.info("Uploading on backend at {}".format(myslice_dict['backend']))
1792 uploader = ManifoldUploader (logger=self.logger,
1793 url=myslice_dict['backend'],
1794 platform=myslice_dict['platform'],
1795 username=myslice_dict['username'],
1796 password=options.password)
1797 uploader.prompt_all()
1798 (count_all, count_success) = (0, 0)
1799 for (hrn, htype, delegated_credential, filename) in hrn_delegated_credentials:
1801 inspect = Credential(string=delegated_credential)
1802 expire_datetime = inspect.get_expiration()
1803 message = "{} ({}) [exp:{}]".format(hrn, htype, expire_datetime)
1804 if uploader.upload(delegated_credential, message=message):
1807 self.logger.info("Successfully uploaded {}/{} credentials"
1808 .format(count_success, count_all))
1810 # at first I thought we would want to save these,
1811 # like 'sfi delegate does' but on second thought
1812 # it is probably not helpful as people would not
1813 # need to run 'sfi delegate' at all anymore
1814 if count_success != count_all:
1816 # xxx should analyze result
1819 @declare_command("cred", "")
1820 def trusted(self, options, args):
1822 return the trusted certs at this interface (get_trusted_certs)
1824 if options.registry_interface:
1825 server = self.registry()
1827 server = self.sliceapi()
1828 cred = self.my_authority_credential_string()
1829 trusted_certs = server.get_trusted_certs(cred)
1830 if not options.registry_interface:
1831 trusted_certs = ReturnValue.get_value(trusted_certs)
1833 for trusted_cert in trusted_certs:
1834 print("\n===========================================================\n")
1835 gid = GID(string=trusted_cert)
1837 cert = Certificate(string=trusted_cert)
1838 self.logger.debug('Sfi.trusted -> {}'.format(cert.get_subject()))
1839 print("Certificate:\n{}\n\n".format(trusted_cert))
1840 # xxx should analyze result