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
35 from sfa.storage.record import Record
37 from sfa.rspecs.rspec import RSpec
38 from sfa.rspecs.rspec_converter import RSpecConverter
39 from sfa.rspecs.version_manager import VersionManager
41 from sfa.client.sfaclientlib import SfaClientBootstrap
42 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
43 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
44 from sfa.client.return_value import ReturnValue
45 from sfa.client.candidates import Candidates
49 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
50 terminal_render, filter_records
53 def display_rspec(rspec, format='rspec'):
55 tree = etree.parse(StringIO(rspec))
57 result = root.xpath("./network/site/node/hostname/text()")
58 elif format in ['ip']:
59 # The IP address is not yet part of the new RSpec
60 # so this doesn't do anything yet.
61 tree = etree.parse(StringIO(rspec))
63 result = root.xpath("./network/site/node/ipv4/text()")
70 def display_list(results):
71 for result in results:
74 def display_records(recordList, dump=False):
75 ''' Print all fields in the record'''
76 for record in recordList:
77 display_record(record, dump)
79 def display_record(record, dump=False):
81 record.dump(sort=True)
83 info = record.getdict()
84 print "%s (%s)" % (info['hrn'], info['type'])
88 def filter_records(type, records):
90 for record in records:
91 if (record['type'] == type) or (type == "all"):
92 filtered_records.append(record)
93 return filtered_records
96 def credential_printable (cred):
97 credential=Credential(cred=cred)
99 result += credential.get_summary_tostring()
101 rights = credential.get_privileges()
102 result += "type=%s\n" % credential.type
103 result += "version=%s\n" % credential.version
104 result += "rights=%s\n"%rights
107 def show_credentials (cred_s):
108 if not isinstance (cred_s,list): cred_s = [cred_s]
110 print "Using Credential %s"%credential_printable(cred)
113 def save_raw_to_file(var, filename, format="text", banner=None):
115 # if filename is "-", send it to stdout
118 f = open(filename, "w")
123 elif format == "pickled":
124 f.write(pickle.dumps(var))
125 elif format == "json":
126 if hasattr(json, "dumps"):
127 f.write(json.dumps(var)) # python 2.6
129 f.write(json.write(var)) # python 2.5
131 # this should never happen
132 print "unknown output format", format
134 f.write('\n'+banner+"\n")
136 def save_rspec_to_file(rspec, filename):
137 if not filename.endswith(".rspec"):
138 filename = filename + ".rspec"
139 f = open(filename, 'w')
144 def save_records_to_file(filename, record_dicts, format="xml"):
147 for record_dict in record_dicts:
149 save_record_to_file(filename + "." + str(index), record_dict)
151 save_record_to_file(filename, record_dict)
153 elif format == "xmllist":
154 f = open(filename, "w")
155 f.write("<recordlist>\n")
156 for record_dict in record_dicts:
157 record_obj=Record(dict=record_dict)
158 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
159 f.write("</recordlist>\n")
161 elif format == "hrnlist":
162 f = open(filename, "w")
163 for record_dict in record_dicts:
164 record_obj=Record(dict=record_dict)
165 f.write(record_obj.hrn + "\n")
168 # this should never happen
169 print "unknown output format", format
171 def save_record_to_file(filename, record_dict):
172 record = Record(dict=record_dict)
173 xml = record.save_as_xml()
174 f=codecs.open(filename, encoding='utf-8',mode="w")
179 # minimally check a key argument
180 def check_ssh_key (key):
181 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
182 return re.match(good_ssh_key, key, re.IGNORECASE)
185 def load_record_from_opts(options):
187 if hasattr(options, 'xrn') and options.xrn:
188 if hasattr(options, 'type') and options.type:
189 xrn = Xrn(options.xrn, options.type)
191 xrn = Xrn(options.xrn)
192 record_dict['urn'] = xrn.get_urn()
193 record_dict['hrn'] = xrn.get_hrn()
194 record_dict['type'] = xrn.get_type()
195 if hasattr(options, 'key') and options.key:
197 pubkey = open(options.key, 'r').read()
200 if not check_ssh_key (pubkey):
201 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
202 record_dict['keys'] = [pubkey]
203 if hasattr(options, 'slices') and options.slices:
204 record_dict['slices'] = options.slices
205 if hasattr(options, 'researchers') and options.researchers:
206 record_dict['researcher'] = options.researchers
207 if hasattr(options, 'email') and options.email:
208 record_dict['email'] = options.email
209 if hasattr(options, 'pis') and options.pis:
210 record_dict['pi'] = options.pis
212 # handle extra settings
213 record_dict.update(options.extras)
215 return Record(dict=record_dict)
217 def load_record_from_file(filename):
218 f=codecs.open(filename, encoding="utf-8", mode="r")
219 xml_string = f.read()
221 return Record(xml=xml_string)
225 def unique_call_id(): return uuid.uuid4().urn
229 # dirty hack to make this class usable from the outside
230 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
233 def default_sfi_dir ():
234 if os.path.isfile("./sfi_config"):
237 return os.path.expanduser("~/.sfi/")
239 # dummy to meet Sfi's expectations for its 'options' field
240 # i.e. s/t we can do setattr on
244 def __init__ (self,options=None):
245 if options is None: options=Sfi.DummyOptions()
246 for opt in Sfi.required_options:
247 if not hasattr(options,opt): setattr(options,opt,None)
248 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
249 self.options = options
251 self.authority = None
252 self.logger = sfi_logger
253 self.logger.enable_console()
254 self.available_names = [ tuple[0] for tuple in Sfi.available ]
255 self.available_dict = dict (Sfi.available)
257 # tuples command-name expected-args in the order in which they should appear in the help
260 ("list", "authority"),
263 ("update", "[record]"),
266 ("describe", "slice_hrn"),
267 ("allocate", "slice_hrn rspec"),
268 ("provision", "slice_hrn"),
269 ("action", "slice_hrn action"),
270 ("delete", "slice_hrn"),
271 ("status", "slice_hrn"),
272 ("renew", "slice_hrn time"),
273 ("shutdown", "slice_hrn"),
274 ("delegate", "to_hrn"),
280 def print_command_help (self, options):
281 verbose=getattr(options,'verbose')
282 format3="%18s %-15s %s"
285 print format3%("command","cmd_args","description")
289 self.create_parser().print_help()
290 for command in self.available_names:
291 args=self.available_dict[command]
292 method=getattr(self,command,None)
294 if method: doc=getattr(method,'__doc__',"")
295 if not doc: doc="*** no doc found ***"
296 doc=doc.strip(" \t\n")
297 doc=doc.replace("\n","\n"+35*' ')
300 print format3%(command,args,doc)
302 self.create_command_parser(command).print_help()
304 def create_command_parser(self, command):
305 if command not in self.available_dict:
306 msg="Invalid command\n"
308 msg += ','.join(self.available_names)
309 self.logger.critical(msg)
312 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
313 % (command, self.available_dict[command]))
315 if command in ("add", "update"):
316 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
317 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
318 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
319 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
321 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
322 default='', type="str", action='callback', callback=optparse_listvalue_callback)
323 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
324 help='Set/replace slice researchers', default='', type="str", action='callback',
325 callback=optparse_listvalue_callback)
326 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
327 default='', type="str", action='callback', callback=optparse_listvalue_callback)
328 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
329 action="callback", callback=optparse_dictvalue_callback, nargs=1,
330 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
332 # user specifies remote aggregate/sm/component
333 if command in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
334 "action", "shutdown", "renew", "status"):
335 parser.add_option("-d", "--delegate", dest="delegate", default=None,
337 help="Include a credential delegated to the user's root"+\
338 "authority in set of credentials for this call")
340 # show_credential option
341 if command in ("list","resources", "describe", "provision", "allocate", "add","update","remove","slices","delete","status","renew"):
342 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
343 help="show credential(s) used in human-readable form")
344 # registy filter option
345 if command in ("list", "show", "remove"):
346 parser.add_option("-t", "--type", dest="type", type="choice",
347 help="type filter ([all]|user|slice|authority|node|aggregate)",
348 choices=("all", "user", "slice", "authority", "node", "aggregate"),
350 if command in ("show"):
351 parser.add_option("-k","--key",dest="keys",action="append",default=[],
352 help="specify specific keys to be displayed from record")
353 if command in ("resources", "describe"):
355 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
356 help="schema type and version of resulting RSpec")
357 # disable/enable cached rspecs
358 parser.add_option("-c", "--current", dest="current", default=False,
360 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
362 parser.add_option("-f", "--format", dest="format", type="choice",
363 help="display format ([xml]|dns|ip)", default="xml",
364 choices=("xml", "dns", "ip"))
365 #panos: a new option to define the type of information about resources a user is interested in
366 parser.add_option("-i", "--info", dest="info",
367 help="optional component information", default=None)
368 # a new option to retreive or not reservation-oriented RSpecs (leases)
369 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
370 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
371 choices=("all", "resources", "leases"), default="resources")
374 if command in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
375 parser.add_option("-o", "--output", dest="file",
376 help="output XML to file", metavar="FILE", default=None)
378 if command in ("show", "list"):
379 parser.add_option("-f", "--format", dest="format", type="choice",
380 help="display format ([text]|xml)", default="text",
381 choices=("text", "xml"))
383 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
384 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
385 choices=("xml", "xmllist", "hrnlist"))
386 if command == 'list':
387 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
388 help="list all child records", default=False)
389 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
390 help="gives details, like user keys", default=False)
391 if command in ("delegate"):
392 parser.add_option("-u", "--user",
393 action="store_true", dest="delegate_user", default=False,
394 help="delegate your own credentials; default if no other option is provided")
395 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
396 metavar="slice_hrn", help="delegate cred. for slice HRN")
397 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
398 metavar='auth_hrn', help="delegate cred for auth HRN")
399 # this primarily is a shorthand for -a my_hrn^
400 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
401 help="delegate your PI credentials, so s.t. like -a your_hrn^")
402 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
403 help="""by default the mandatory argument is expected to be a user,
404 use this if you mean an authority instead""")
406 if command in ("version"):
407 parser.add_option("-R","--registry-version",
408 action="store_true", dest="version_registry", default=False,
409 help="probe registry version instead of sliceapi")
410 parser.add_option("-l","--local",
411 action="store_true", dest="version_local", default=False,
412 help="display version of the local client")
417 def create_parser(self):
419 # Generate command line parser
420 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
421 description="Commands: %s"%(" ".join(self.available_names)))
422 parser.add_option("-r", "--registry", dest="registry",
423 help="root registry", metavar="URL", default=None)
424 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
425 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
426 parser.add_option("-R", "--raw", dest="raw", default=None,
427 help="Save raw, unparsed server response to a file")
428 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
429 help="raw file format ([text]|pickled|json)", default="text",
430 choices=("text","pickled","json"))
431 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
432 help="text string to write before and after raw output")
433 parser.add_option("-d", "--dir", dest="sfi_dir",
434 help="config & working directory - default is %default",
435 metavar="PATH", default=Sfi.default_sfi_dir())
436 parser.add_option("-u", "--user", dest="user",
437 help="user name", metavar="HRN", default=None)
438 parser.add_option("-a", "--auth", dest="auth",
439 help="authority name", metavar="HRN", default=None)
440 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
441 help="verbose mode - cumulative")
442 parser.add_option("-D", "--debug",
443 action="store_true", dest="debug", default=False,
444 help="Debug (xml-rpc) protocol messages")
445 # would it make sense to use ~/.ssh/id_rsa as a default here ?
446 parser.add_option("-k", "--private-key",
447 action="store", dest="user_private_key", default=None,
448 help="point to the private key file to use if not yet installed in sfi_dir")
449 parser.add_option("-t", "--timeout", dest="timeout", default=None,
450 help="Amout of time to wait before timing out the request")
451 parser.add_option("-?", "--commands",
452 action="store_true", dest="command_help", default=False,
453 help="one page summary on commands & exit")
454 parser.disable_interspersed_args()
459 def print_help (self):
460 print "==================== Generic sfi usage"
461 self.sfi_parser.print_help()
462 print "==================== Specific command usage"
463 self.command_parser.print_help()
466 # Main: parse arguments and dispatch to command
468 def dispatch(self, command, command_options, command_args):
469 method=getattr(self, command,None)
471 print "Unknown command %s"%command
473 return method(command_options, command_args)
476 self.sfi_parser = self.create_parser()
477 (options, args) = self.sfi_parser.parse_args()
478 if options.command_help:
479 self.print_command_help(options)
481 self.options = options
483 self.logger.setLevelFromOptVerbose(self.options.verbose)
486 self.logger.critical("No command given. Use -h for help.")
487 self.print_command_help(options)
490 # complete / find unique match with command set
491 command_candidates = Candidates (self.available_names)
493 command = command_candidates.only_match(input)
495 self.print_command_help(options)
497 # second pass options parsing
498 self.command_parser = self.create_command_parser(command)
499 (command_options, command_args) = self.command_parser.parse_args(args[1:])
500 self.command_options = command_options
504 self.logger.debug("Command=%s" % command)
507 self.dispatch(command, command_options, command_args)
511 self.logger.log_exc ("sfi command %s failed"%command)
517 def read_config(self):
518 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
519 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
521 if Config.is_ini(config_file):
522 config = Config (config_file)
524 # try upgrading from shell config format
525 fp, fn = mkstemp(suffix='sfi_config', text=True)
527 # we need to preload the sections we want parsed
528 # from the shell config
529 config.add_section('sfi')
530 config.add_section('sface')
531 config.load(config_file)
533 shutil.move(config_file, shell_config_file)
535 config.save(config_file)
538 self.logger.critical("Failed to read configuration file %s"%config_file)
539 self.logger.info("Make sure to remove the export clauses and to add quotes")
540 if self.options.verbose==0:
541 self.logger.info("Re-run with -v for more details")
543 self.logger.log_exc("Could not read config file %s"%config_file)
548 if (self.options.sm is not None):
549 self.sm_url = self.options.sm
550 elif hasattr(config, "SFI_SM"):
551 self.sm_url = config.SFI_SM
553 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
557 if (self.options.registry is not None):
558 self.reg_url = self.options.registry
559 elif hasattr(config, "SFI_REGISTRY"):
560 self.reg_url = config.SFI_REGISTRY
562 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
566 if (self.options.user is not None):
567 self.user = self.options.user
568 elif hasattr(config, "SFI_USER"):
569 self.user = config.SFI_USER
571 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
575 if (self.options.auth is not None):
576 self.authority = self.options.auth
577 elif hasattr(config, "SFI_AUTH"):
578 self.authority = config.SFI_AUTH
580 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
583 self.config_file=config_file
587 def show_config (self):
588 print "From configuration file %s"%self.config_file
591 ('SFI_AUTH','authority'),
593 ('SFI_REGISTRY','reg_url'),
595 for (external_name, internal_name) in flags:
596 print "%s='%s'"%(external_name,getattr(self,internal_name))
599 # Get various credential and spec files
601 # Establishes limiting conventions
602 # - conflates MAs and SAs
603 # - assumes last token in slice name is unique
605 # Bootstraps credentials
606 # - bootstrap user credential from self-signed certificate
607 # - bootstrap authority credential from user credential
608 # - bootstrap slice credential from user credential
611 # init self-signed cert, user credentials and gid
612 def bootstrap (self):
613 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
615 # if -k is provided, use this to initialize private key
616 if self.options.user_private_key:
617 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
619 # trigger legacy compat code if needed
620 # the name has changed from just <leaf>.pkey to <hrn>.pkey
621 if not os.path.isfile(client_bootstrap.private_key_filename()):
622 self.logger.info ("private key not found, trying legacy name")
624 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
625 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
626 client_bootstrap.init_private_key_if_missing (legacy_private_key)
627 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
629 self.logger.log_exc("Can't find private key ")
633 client_bootstrap.bootstrap_my_gid()
634 # extract what's needed
635 self.private_key = client_bootstrap.private_key()
636 self.my_credential_string = client_bootstrap.my_credential_string ()
637 self.my_credential = {'geni_type': 'geni_sfa',
638 'geni_version': '3.0',
639 'geni_value': self.my_credential_string}
640 self.my_gid = client_bootstrap.my_gid ()
641 self.client_bootstrap = client_bootstrap
644 def my_authority_credential_string(self):
645 if not self.authority:
646 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
648 return self.client_bootstrap.authority_credential_string (self.authority)
650 def authority_credential_string(self, auth_hrn):
651 return self.client_bootstrap.authority_credential_string (auth_hrn)
653 def slice_credential_string(self, name):
654 return self.client_bootstrap.slice_credential_string (name)
656 def slice_credential(self, name):
657 return {'geni_type': 'geni_sfa',
658 'geni_version': '3.0',
659 'geni_value': self.slice_credential_string(name)}
661 # xxx should be supported by sfaclientbootstrap as well
662 def delegate_cred(self, object_cred, hrn, type='authority'):
663 # the gid and hrn of the object we are delegating
664 if isinstance(object_cred, str):
665 object_cred = Credential(string=object_cred)
666 object_gid = object_cred.get_gid_object()
667 object_hrn = object_gid.get_hrn()
669 if not object_cred.get_privileges().get_all_delegate():
670 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
673 # the delegating user's gid
674 caller_gidfile = self.my_gid()
676 # the gid of the user who will be delegated to
677 delegee_gid = self.client_bootstrap.gid(hrn,type)
678 delegee_hrn = delegee_gid.get_hrn()
679 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
680 return dcred.save_to_string(save_parents=True)
683 # Management of the servers
688 if not hasattr (self, 'registry_proxy'):
689 self.logger.info("Contacting Registry at: %s"%self.reg_url)
690 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
691 timeout=self.options.timeout, verbose=self.options.debug)
692 return self.registry_proxy
696 if not hasattr (self, 'sliceapi_proxy'):
697 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
698 if hasattr(self.command_options,'component') and self.command_options.component:
699 # resolve the hrn at the registry
700 node_hrn = self.command_options.component
701 records = self.registry().Resolve(node_hrn, self.my_credential_string)
702 records = filter_records('node', records)
704 self.logger.warning("No such component:%r"% opts.component)
706 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
707 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
709 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
710 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
711 self.sm_url = 'http://' + self.sm_url
712 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
713 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
714 timeout=self.options.timeout, verbose=self.options.debug)
715 return self.sliceapi_proxy
717 def get_cached_server_version(self, server):
718 # check local cache first
721 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
722 cache_key = server.url + "-version"
724 cache = Cache(cache_file)
727 self.logger.info("Local cache not found at: %s" % cache_file)
730 version = cache.get(cache_key)
733 result = server.GetVersion()
734 version= ReturnValue.get_value(result)
735 # cache version for 20 minutes
736 cache.add(cache_key, version, ttl= 60*20)
737 self.logger.info("Updating cache file %s" % cache_file)
738 cache.save_to_file(cache_file)
742 ### resurrect this temporarily so we can support V1 aggregates for a while
743 def server_supports_options_arg(self, server):
745 Returns true if server support the optional call_id arg, false otherwise.
747 server_version = self.get_cached_server_version(server)
749 # xxx need to rewrite this
750 if int(server_version.get('geni_api')) >= 2:
754 def server_supports_call_id_arg(self, server):
755 server_version = self.get_cached_server_version(server)
757 if 'sfa' in server_version and 'code_tag' in server_version:
758 code_tag = server_version['code_tag']
759 code_tag_parts = code_tag.split("-")
760 version_parts = code_tag_parts[0].split(".")
761 major, minor = version_parts[0], version_parts[1]
762 rev = code_tag_parts[1]
763 if int(major) == 1 and minor == 0 and build >= 22:
767 ### ois = options if supported
768 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
769 def ois (self, server, option_dict):
770 if self.server_supports_options_arg (server):
772 elif self.server_supports_call_id_arg (server):
773 return [ unique_call_id () ]
777 ### cis = call_id if supported - like ois
778 def cis (self, server):
779 if self.server_supports_call_id_arg (server):
780 return [ unique_call_id ]
784 ######################################## miscell utilities
785 def get_rspec_file(self, rspec):
786 if (os.path.isabs(rspec)):
789 file = os.path.join(self.options.sfi_dir, rspec)
790 if (os.path.isfile(file)):
793 self.logger.critical("No such rspec file %s"%rspec)
796 def get_record_file(self, record):
797 if (os.path.isabs(record)):
800 file = os.path.join(self.options.sfi_dir, record)
801 if (os.path.isfile(file)):
804 self.logger.critical("No such registry record file %s"%record)
808 #==========================================================================
809 # Following functions implement the commands
811 # Registry-related commands
812 #==========================================================================
814 def version(self, options, args):
816 display an SFA server version (GetVersion)
817 or version information about sfi itself
819 if options.version_local:
820 version=version_core()
822 if options.version_registry:
823 server=self.registry()
825 server = self.sliceapi()
826 result = server.GetVersion()
827 version = ReturnValue.get_value(result)
829 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
831 pprinter = PrettyPrinter(indent=4)
832 pprinter.pprint(version)
834 def list(self, options, args):
836 list entries in named authority registry (List)
843 if options.recursive:
844 opts['recursive'] = options.recursive
846 if options.show_credential:
847 show_credentials(self.my_credential_string)
849 list = self.registry().List(hrn, self.my_credential_string, options)
851 raise Exception, "Not enough parameters for the 'list' command"
853 # filter on person, slice, site, node, etc.
854 # This really should be in the self.filter_records funct def comment...
855 list = filter_records(options.type, list)
856 terminal_render (list, options)
858 save_records_to_file(options.file, list, options.fileformat)
861 def show(self, options, args):
863 show details about named registry record (Resolve)
869 # explicitly require Resolve to run in details mode
870 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
871 record_dicts = filter_records(options.type, record_dicts)
873 self.logger.error("No record of type %s"% options.type)
875 # user has required to focus on some keys
877 def project (record):
879 for key in options.keys:
880 try: projected[key]=record[key]
883 record_dicts = [ project (record) for record in record_dicts ]
884 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
885 for record in records:
886 if (options.format == "text"): record.dump(sort=True)
887 else: print record.save_as_xml()
889 save_records_to_file(options.file, record_dicts, options.fileformat)
892 def add(self, options, args):
893 "add record into registry by using the command options (Recommended) or from xml file (Register)"
894 auth_cred = self.my_authority_credential_string()
895 if options.show_credential:
896 show_credentials(auth_cred)
903 record_filepath = args[0]
904 rec_file = self.get_record_file(record_filepath)
905 record_dict.update(load_record_from_file(rec_file).todict())
907 print "Cannot load record file %s"%record_filepath
910 record_dict.update(load_record_from_opts(options).todict())
911 # we should have a type by now
912 if 'type' not in record_dict :
915 # this is still planetlab dependent.. as plc will whine without that
916 # also, it's only for adding
917 if record_dict['type'] == 'user':
918 if not 'first_name' in record_dict:
919 record_dict['first_name'] = record_dict['hrn']
920 if 'last_name' not in record_dict:
921 record_dict['last_name'] = record_dict['hrn']
922 return self.registry().Register(record_dict, auth_cred)
924 def update(self, options, args):
925 "update record into registry by using the command options (Recommended) or from xml file (Update)"
928 record_filepath = args[0]
929 rec_file = self.get_record_file(record_filepath)
930 record_dict.update(load_record_from_file(rec_file).todict())
932 record_dict.update(load_record_from_opts(options).todict())
933 # at the very least we need 'type' here
934 if 'type' not in record_dict:
938 # don't translate into an object, as this would possibly distort
939 # user-provided data; e.g. add an 'email' field to Users
940 if record_dict['type'] == "user":
941 if record_dict['hrn'] == self.user:
942 cred = self.my_credential_string
944 cred = self.my_authority_credential_string()
945 elif record_dict['type'] in ["slice"]:
947 cred = self.slice_credential_string(record_dict['hrn'])
948 except ServerException, e:
949 # XXX smbaker -- once we have better error return codes, update this
950 # to do something better than a string compare
951 if "Permission error" in e.args[0]:
952 cred = self.my_authority_credential_string()
955 elif record_dict['type'] in ["authority"]:
956 cred = self.my_authority_credential_string()
957 elif record_dict['type'] == 'node':
958 cred = self.my_authority_credential_string()
960 raise "unknown record type" + record_dict['type']
961 if options.show_credential:
962 show_credentials(cred)
963 return self.registry().Update(record_dict, cred)
965 def remove(self, options, args):
966 "remove registry record by name (Remove)"
967 auth_cred = self.my_authority_credential_string()
975 if options.show_credential:
976 show_credentials(auth_cred)
977 return self.registry().Remove(hrn, auth_cred, type)
979 # ==================================================================
980 # Slice-related commands
981 # ==================================================================
983 def slices(self, options, args):
984 "list instantiated slices (ListSlices) - returns urn's"
985 server = self.sliceapi()
987 creds = [self.my_credential_string]
988 # options and call_id when supported
990 api_options['call_id']=unique_call_id()
991 if options.show_credential:
992 show_credentials(creds)
993 result = server.ListSlices(creds, *self.ois(server,api_options))
994 value = ReturnValue.get_value(result)
996 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1001 # show rspec for named slice
1002 def resources(self, options, args):
1004 discover available resources (ListResources)
1006 server = self.sliceapi()
1009 creds = [self.my_credential]
1010 if options.delegate:
1011 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1012 if options.show_credential:
1013 show_credentials(creds)
1015 # no need to check if server accepts the options argument since the options has
1016 # been a required argument since v1 API
1018 # always send call_id to v2 servers
1019 api_options ['call_id'] = unique_call_id()
1020 # ask for cached value if available
1021 api_options ['cached'] = True
1023 api_options['info'] = options.info
1024 if options.list_leases:
1025 api_options['list_leases'] = options.list_leases
1027 if options.current == True:
1028 api_options['cached'] = False
1030 api_options['cached'] = True
1031 if options.rspec_version:
1032 version_manager = VersionManager()
1033 server_version = self.get_cached_server_version(server)
1034 if 'sfa' in server_version:
1035 # just request the version the client wants
1036 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1038 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1040 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1041 result = server.ListResources (creds, api_options)
1042 value = ReturnValue.get_value(result)
1043 if self.options.raw:
1044 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1045 if options.file is not None:
1046 save_rspec_to_file(value, options.file)
1047 if (self.options.raw is None) and (options.file is None):
1048 display_rspec(value, options.format)
1052 def describe(self, options, args):
1054 shows currently allocated/provisioned resources of the named slice or set of slivers (Describe)
1056 server = self.sliceapi()
1059 creds = [self.slice_credential(args[0])]
1060 if options.delegate:
1061 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1062 if options.show_credential:
1063 show_credentials(creds)
1065 api_options = {'call_id': unique_call_id(),
1067 'info': options.info,
1068 'list_leases': options.list_leases,
1069 'geni_rspec_version': {'type': 'geni', 'version': '3.0'},
1071 if options.rspec_version:
1072 version_manager = VersionManager()
1073 server_version = self.get_cached_server_version(server)
1074 if 'sfa' in server_version:
1075 # just request the version the client wants
1076 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1078 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1079 urn = Xrn(args[0], type='slice').get_urn()
1080 result = server.Describe([urn], creds, api_options)
1081 value = ReturnValue.get_value(result)
1082 if self.options.raw:
1083 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1084 if options.file is not None:
1085 save_rspec_to_file(value, options.file)
1086 if (self.options.raw is None) and (options.file is None):
1087 display_rspec(value, options.format)
1091 def delete(self, options, args):
1093 de-allocate and de-provision all or named slivers of the slice (Delete)
1095 server = self.sliceapi()
1099 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1102 slice_cred = self.slice_credential(slice_hrn)
1103 creds = [slice_cred]
1105 # options and call_id when supported
1107 api_options ['call_id'] = unique_call_id()
1108 if options.show_credential:
1109 show_credentials(creds)
1110 result = server.Delete([slice_urn], creds, *self.ois(server, api_options ) )
1111 value = ReturnValue.get_value(result)
1112 if self.options.raw:
1113 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1118 def allocate(self, options, args):
1120 allocate resources to the named slice (Allocate)
1122 server = self.sliceapi()
1123 server_version = self.get_cached_server_version(server)
1125 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1128 creds = [self.slice_credential(slice_hrn)]
1130 delegated_cred = None
1131 if server_version.get('interface') == 'slicemgr':
1132 # delegate our cred to the slice manager
1133 # do not delegate cred to slicemgr...not working at the moment
1135 #if server_version.get('hrn'):
1136 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1137 #elif server_version.get('urn'):
1138 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1140 if options.show_credential:
1141 show_credentials(creds)
1144 rspec_file = self.get_rspec_file(args[1])
1145 rspec = open(rspec_file).read()
1147 api_options ['call_id'] = unique_call_id()
1148 result = server.Allocate(slice_urn, creds, rspec, api_options)
1149 value = ReturnValue.get_value(result)
1150 if self.options.raw:
1151 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1152 if options.file is not None:
1153 save_rspec_to_file (value, options.file)
1154 if (self.options.raw is None) and (options.file is None):
1160 def provision(self, options, args):
1162 provision already allocated resources of named slice (Provision)
1164 server = self.sliceapi()
1165 server_version = self.get_cached_server_version(server)
1167 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1170 creds = [self.slice_credential(slice_hrn)]
1171 delegated_cred = None
1172 if server_version.get('interface') == 'slicemgr':
1173 # delegate our cred to the slice manager
1174 # do not delegate cred to slicemgr...not working at the moment
1176 #if server_version.get('hrn'):
1177 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1178 #elif server_version.get('urn'):
1179 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1181 if options.show_credential:
1182 show_credentials(creds)
1185 api_options ['call_id'] = unique_call_id()
1187 # set the requtested rspec version
1188 version_manager = VersionManager()
1189 rspec_version = version_manager._get_version('geni', '3.0').to_dict()
1190 api_options['geni_rspec_version'] = rspec_version
1193 # need to pass along user keys to the aggregate.
1195 # { urn: urn:publicid:IDN+emulab.net+user+alice
1196 # keys: [<ssh key A>, <ssh key B>]
1199 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1200 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1201 slice_record = slice_records[0]
1202 user_hrns = slice_record['researcher']
1203 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1204 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1205 users = pg_users_arg(user_records)
1207 api_options['geni_users'] = users
1208 result = server.Provision([slice_urn], creds, api_options)
1209 value = ReturnValue.get_value(result)
1210 if self.options.raw:
1211 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1212 if options.file is not None:
1213 save_rspec_to_file (value, options.file)
1214 if (self.options.raw is None) and (options.file is None):
1218 def status(self, options, args):
1220 retrieve the status of the slivers belonging to tne named slice (Status)
1222 server = self.sliceapi()
1226 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1229 slice_cred = self.slice_credential(slice_hrn)
1230 creds = [slice_cred]
1232 # options and call_id when supported
1234 api_options['call_id']=unique_call_id()
1235 if options.show_credential:
1236 show_credentials(creds)
1237 result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1238 value = ReturnValue.get_value(result)
1239 if self.options.raw:
1240 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1245 def action(self, options, args):
1247 Perform the named operational action on the named slivers
1249 server = self.sliceapi()
1254 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1256 slice_cred = self.slice_credential(args[0])
1257 creds = [slice_cred]
1258 if options.delegate:
1259 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1260 creds.append(delegated_cred)
1262 result = server.PerformOperationalAction([slice_urn], creds, action , api_options)
1263 value = ReturnValue.get_value(result)
1264 if self.options.raw:
1265 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1270 def renew(self, options, args):
1272 renew slice (RenewSliver)
1274 server = self.sliceapi()
1278 [ slice_hrn, input_time ] = args
1280 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1281 # time: don't try to be smart on the time format, server-side will
1283 slice_cred = self.slice_credential(args[0])
1284 creds = [slice_cred]
1285 # options and call_id when supported
1287 api_options['call_id']=unique_call_id()
1288 if options.show_credential:
1289 show_credentials(creds)
1290 result = server.Renew([slice_urn], creds, input_time, *self.ois(server,api_options))
1291 value = ReturnValue.get_value(result)
1292 if self.options.raw:
1293 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1299 def shutdown(self, options, args):
1301 shutdown named slice (Shutdown)
1303 server = self.sliceapi()
1306 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1308 slice_cred = self.slice_credential(slice_hrn)
1309 creds = [slice_cred]
1310 result = server.Shutdown(slice_urn, creds)
1311 value = ReturnValue.get_value(result)
1312 if self.options.raw:
1313 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1319 def gid(self, options, args):
1321 Create a GID (CreateGid)
1326 target_hrn = args[0]
1327 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1328 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1330 filename = options.file
1332 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1333 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1334 GID(string=gid).save_to_file(filename)
1337 def delegate (self, options, args):
1339 (locally) create delegate credential for use by given hrn
1345 # support for several delegations in the same call
1346 # so first we gather the things to do
1348 for slice_hrn in options.delegate_slices:
1349 message="%s.slice"%slice_hrn
1350 original = self.slice_credential_string(slice_hrn)
1351 tuples.append ( (message, original,) )
1352 if options.delegate_pi:
1353 my_authority=self.authority
1354 message="%s.pi"%my_authority
1355 original = self.my_authority_credential_string()
1356 tuples.append ( (message, original,) )
1357 for auth_hrn in options.delegate_auths:
1358 message="%s.auth"%auth_hrn
1359 original=self.authority_credential_string(auth_hrn)
1360 tuples.append ( (message, original, ) )
1361 # if nothing was specified at all at this point, let's assume -u
1362 if not tuples: options.delegate_user=True
1364 if options.delegate_user:
1365 message="%s.user"%self.user
1366 original = self.my_credential_string
1367 tuples.append ( (message, original, ) )
1369 # default type for beneficial is user unless -A
1370 if options.delegate_to_authority: to_type='authority'
1371 else: to_type='user'
1373 # let's now handle all this
1374 # it's all in the filenaming scheme
1375 for (message,original) in tuples:
1376 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1377 delegated_credential = Credential (string=delegated_string)
1378 filename = os.path.join ( self.options.sfi_dir,
1379 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1380 delegated_credential.save_to_file(filename, save_parents=True)
1381 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1383 def trusted(self, options, args):
1385 return uhe trusted certs at this interface (get_trusted_certs)
1387 trusted_certs = self.registry().get_trusted_certs()
1388 for trusted_cert in trusted_certs:
1389 gid = GID(string=trusted_cert)
1391 cert = Certificate(string=trusted_cert)
1392 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1395 def config (self, options, args):
1396 "Display contents of current config"