2 # sfi.py - basic SFA command-line client
3 # this module is also used in sfascan
16 from lxml import etree
17 from StringIO import StringIO
18 from optparse import OptionParser
19 from pprint import PrettyPrinter
21 from sfa.trust.certificate import Keypair, Certificate
22 from sfa.trust.gid import GID
23 from sfa.trust.credential import Credential
24 from sfa.trust.sfaticket import SfaTicket
26 from sfa.util.faults import SfaInvalidArgument
27 from sfa.util.sfalogging import sfi_logger
28 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
29 from sfa.util.config import Config
30 from sfa.util.version import version_core
31 from sfa.util.cache import Cache
33 from sfa.storage.record import Record
35 from sfa.rspecs.rspec import RSpec
36 from sfa.rspecs.rspec_converter import RSpecConverter
37 from sfa.rspecs.version_manager import VersionManager
39 from sfa.client.sfaclientlib import SfaClientBootstrap
40 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
41 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
42 from sfa.client.return_value import ReturnValue
43 from sfa.client.candidates import Candidates
47 # utility methods here
48 def optparse_listvalue_callback(option, option_string, value, parser):
49 setattr(parser.values, option.dest, value.split(','))
51 # a code fragment that could be helpful for argparse which unfortunately is
52 # available with 2.7 only, so this feels like too strong a requirement for the client side
53 #class ExtraArgAction (argparse.Action):
54 # def __call__ (self, parser, namespace, values, option_string=None):
55 # would need a try/except of course
56 # (k,v)=values.split('=')
57 # d=getattr(namespace,self.dest)
60 #parser.add_argument ("-X","--extra",dest='extras', default={}, action=ExtraArgAction,
61 # help="set extra flags, testbed dependent, e.g. --extra enabled=true")
63 def optparse_dictvalue_callback (option, option_string, value, parser):
65 (k,v)=value.split('=',1)
66 d=getattr(parser.values, option.dest)
73 def display_rspec(rspec, format='rspec'):
75 tree = etree.parse(StringIO(rspec))
77 result = root.xpath("./network/site/node/hostname/text()")
78 elif format in ['ip']:
79 # The IP address is not yet part of the new RSpec
80 # so this doesn't do anything yet.
81 tree = etree.parse(StringIO(rspec))
83 result = root.xpath("./network/site/node/ipv4/text()")
90 def display_list(results):
91 for result in results:
94 def display_records(recordList, dump=False):
95 ''' Print all fields in the record'''
96 for record in recordList:
97 display_record(record, dump)
99 def display_record(record, dump=False):
101 record.dump(sort=True)
103 info = record.getdict()
104 print "%s (%s)" % (info['hrn'], info['type'])
108 def filter_records(type, records):
109 filtered_records = []
110 for record in records:
111 if (record['type'] == type) or (type == "all"):
112 filtered_records.append(record)
113 return filtered_records
116 def credential_printable (credential_string):
117 credential=Credential(string=credential_string)
119 result += credential.get_summary_tostring()
121 rights = credential.get_privileges()
122 result += "rights=%s"%rights
126 def show_credentials (cred_s):
127 if not isinstance (cred_s,list): cred_s = [cred_s]
129 print "Using Credential %s"%credential_printable(cred)
132 def save_raw_to_file(var, filename, format="text", banner=None):
134 # if filename is "-", send it to stdout
137 f = open(filename, "w")
142 elif format == "pickled":
143 f.write(pickle.dumps(var))
144 elif format == "json":
145 if hasattr(json, "dumps"):
146 f.write(json.dumps(var)) # python 2.6
148 f.write(json.write(var)) # python 2.5
150 # this should never happen
151 print "unknown output format", format
153 f.write('\n'+banner+"\n")
155 def save_rspec_to_file(rspec, filename):
156 if not filename.endswith(".rspec"):
157 filename = filename + ".rspec"
158 f = open(filename, 'w')
163 def save_records_to_file(filename, record_dicts, format="xml"):
166 for record_dict in record_dicts:
168 save_record_to_file(filename + "." + str(index), record_dict)
170 save_record_to_file(filename, record_dict)
172 elif format == "xmllist":
173 f = open(filename, "w")
174 f.write("<recordlist>\n")
175 for record_dict in record_dicts:
176 record_obj=Record(dict=record_dict)
177 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
178 f.write("</recordlist>\n")
180 elif format == "hrnlist":
181 f = open(filename, "w")
182 for record_dict in record_dicts:
183 record_obj=Record(dict=record_dict)
184 f.write(record_obj.hrn + "\n")
187 # this should never happen
188 print "unknown output format", format
190 def save_record_to_file(filename, record_dict):
191 record = Record(dict=record_dict)
192 xml = record.save_as_xml()
193 f=codecs.open(filename, encoding='utf-8',mode="w")
198 # minimally check a key argument
199 def check_ssh_key (key):
200 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
201 return re.match(good_ssh_key, key, re.IGNORECASE)
204 def load_record_from_opts(options):
206 if hasattr(options, 'xrn') and options.xrn:
207 if hasattr(options, 'type') and options.type:
208 xrn = Xrn(options.xrn, options.type)
210 xrn = Xrn(options.xrn)
211 record_dict['urn'] = xrn.get_urn()
212 record_dict['hrn'] = xrn.get_hrn()
213 record_dict['type'] = xrn.get_type()
214 if hasattr(options, 'key') and options.key:
216 pubkey = open(options.key, 'r').read()
219 if not check_ssh_key (pubkey):
220 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
221 record_dict['keys'] = [pubkey]
222 if hasattr(options, 'slices') and options.slices:
223 record_dict['slices'] = options.slices
224 if hasattr(options, 'researchers') and options.researchers:
225 record_dict['researcher'] = options.researchers
226 if hasattr(options, 'email') and options.email:
227 record_dict['email'] = options.email
228 if hasattr(options, 'pis') and options.pis:
229 record_dict['pi'] = options.pis
231 # handle extra settings
232 record_dict.update(options.extras)
234 return Record(dict=record_dict)
236 def load_record_from_file(filename):
237 f=codecs.open(filename, encoding="utf-8", mode="r")
238 xml_string = f.read()
240 return Record(xml=xml_string)
244 def unique_call_id(): return uuid.uuid4().urn
248 # dirty hack to make this class usable from the outside
249 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
252 def default_sfi_dir ():
253 if os.path.isfile("./sfi_config"):
256 return os.path.expanduser("~/.sfi/")
258 # dummy to meet Sfi's expectations for its 'options' field
259 # i.e. s/t we can do setattr on
263 def __init__ (self,options=None):
264 if options is None: options=Sfi.DummyOptions()
265 for opt in Sfi.required_options:
266 if not hasattr(options,opt): setattr(options,opt,None)
267 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
268 self.options = options
270 self.authority = None
271 self.logger = sfi_logger
272 self.logger.enable_console()
273 self.available_names = [ tuple[0] for tuple in Sfi.available ]
274 self.available_dict = dict (Sfi.available)
276 # tuples command-name expected-args in the order in which they should appear in the help
279 ("list", "authority"),
282 ("update", "record"),
285 ("resources", "[slice_hrn]"),
286 ("create", "slice_hrn rspec"),
287 ("delete", "slice_hrn"),
288 ("status", "slice_hrn"),
289 ("start", "slice_hrn"),
290 ("stop", "slice_hrn"),
291 ("reset", "slice_hrn"),
292 ("renew", "slice_hrn time"),
293 ("shutdown", "slice_hrn"),
294 ("get_ticket", "slice_hrn rspec"),
295 ("redeem_ticket", "ticket"),
296 ("delegate", "name"),
297 ("create_gid", "[name]"),
298 ("get_trusted_certs", "cred"),
302 def print_command_help (self, options):
303 verbose=getattr(options,'verbose')
304 format3="%18s %-15s %s"
307 print format3%("command","cmd_args","description")
311 self.create_parser().print_help()
312 for command in self.available_names:
313 args=self.available_dict[command]
314 method=getattr(self,command,None)
316 if method: doc=getattr(method,'__doc__',"")
317 if not doc: doc="*** no doc found ***"
318 doc=doc.strip(" \t\n")
319 doc=doc.replace("\n","\n"+35*' ')
322 print format3%(command,args,doc)
324 self.create_command_parser(command).print_help()
326 def create_command_parser(self, command):
327 if command not in self.available_dict:
328 msg="Invalid command\n"
330 msg += ','.join(self.available_names)
331 self.logger.critical(msg)
334 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
335 % (command, self.available_dict[command]))
337 if command in ("add", "update"):
338 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
339 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
340 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
341 # use --extra instead
342 # parser.add_option('-u', '--url', dest='url', metavar='<url>', default=None, help="URL, useful for slices")
343 # parser.add_option('-d', '--description', dest='description', metavar='<description>',
344 # help='Description, useful for slices', default=None)
345 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
347 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='slice xrns',
348 default='', type="str", action='callback', callback=optparse_listvalue_callback)
349 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
350 help='slice researchers', default='', type="str", action='callback',
351 callback=optparse_listvalue_callback)
352 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Principal Investigators/Project Managers',
353 default='', type="str", action='callback', callback=optparse_listvalue_callback)
354 # use --extra instead
355 # parser.add_option('-f', '--firstname', dest='firstname', metavar='<firstname>', help='user first name')
356 # parser.add_option('-l', '--lastname', dest='lastname', metavar='<lastname>', help='user last name')
357 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
358 action="callback", callback=optparse_dictvalue_callback, nargs=1,
359 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
361 # user specifies remote aggregate/sm/component
362 if command in ("resources", "slices", "create", "delete", "start", "stop",
363 "restart", "shutdown", "get_ticket", "renew", "status"):
364 parser.add_option("-d", "--delegate", dest="delegate", default=None,
366 help="Include a credential delegated to the user's root"+\
367 "authority in set of credentials for this call")
369 # show_credential option
370 if command in ("list","resources","create","add","update","remove","slices","delete","status","renew"):
371 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
372 help="show credential(s) used in human-readable form")
373 # registy filter option
374 if command in ("list", "show", "remove"):
375 parser.add_option("-t", "--type", dest="type", type="choice",
376 help="type filter ([all]|user|slice|authority|node|aggregate)",
377 choices=("all", "user", "slice", "authority", "node", "aggregate"),
379 if command in ("show"):
380 parser.add_option("-k","--key",dest="keys",action="append",default=[],
381 help="specify specific keys to be displayed from record")
382 if command in ("resources"):
384 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
385 help="schema type and version of resulting RSpec")
386 # disable/enable cached rspecs
387 parser.add_option("-c", "--current", dest="current", default=False,
389 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
391 parser.add_option("-f", "--format", dest="format", type="choice",
392 help="display format ([xml]|dns|ip)", default="xml",
393 choices=("xml", "dns", "ip"))
394 #panos: a new option to define the type of information about resources a user is interested in
395 parser.add_option("-i", "--info", dest="info",
396 help="optional component information", default=None)
397 # a new option to retreive or not reservation-oriented RSpecs (leases)
398 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
399 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
400 choices=("all", "resources", "leases"), default="resources")
403 # 'create' does return the new rspec, makes sense to save that too
404 if command in ("resources", "show", "list", "create_gid", 'create'):
405 parser.add_option("-o", "--output", dest="file",
406 help="output XML to file", metavar="FILE", default=None)
408 if command in ("show", "list"):
409 parser.add_option("-f", "--format", dest="format", type="choice",
410 help="display format ([text]|xml)", default="text",
411 choices=("text", "xml"))
413 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
414 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
415 choices=("xml", "xmllist", "hrnlist"))
416 if command == 'list':
417 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
418 help="list all child records", default=False)
419 if command in ("delegate"):
420 parser.add_option("-u", "--user",
421 action="store_true", dest="delegate_user", default=False,
422 help="delegate user credential")
423 parser.add_option("-s", "--slice", dest="delegate_slice",
424 help="delegate slice credential", metavar="HRN", default=None)
426 if command in ("version"):
427 parser.add_option("-R","--registry-version",
428 action="store_true", dest="version_registry", default=False,
429 help="probe registry version instead of sliceapi")
430 parser.add_option("-l","--local",
431 action="store_true", dest="version_local", default=False,
432 help="display version of the local client")
437 def create_parser(self):
439 # Generate command line parser
440 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
441 description="Commands: %s"%(" ".join(self.available_names)))
442 parser.add_option("-r", "--registry", dest="registry",
443 help="root registry", metavar="URL", default=None)
444 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
445 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
446 parser.add_option("-R", "--raw", dest="raw", default=None,
447 help="Save raw, unparsed server response to a file")
448 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
449 help="raw file format ([text]|pickled|json)", default="text",
450 choices=("text","pickled","json"))
451 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
452 help="text string to write before and after raw output")
453 parser.add_option("-d", "--dir", dest="sfi_dir",
454 help="config & working directory - default is %default",
455 metavar="PATH", default=Sfi.default_sfi_dir())
456 parser.add_option("-u", "--user", dest="user",
457 help="user name", metavar="HRN", default=None)
458 parser.add_option("-a", "--auth", dest="auth",
459 help="authority name", metavar="HRN", default=None)
460 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
461 help="verbose mode - cumulative")
462 parser.add_option("-D", "--debug",
463 action="store_true", dest="debug", default=False,
464 help="Debug (xml-rpc) protocol messages")
465 # would it make sense to use ~/.ssh/id_rsa as a default here ?
466 parser.add_option("-k", "--private-key",
467 action="store", dest="user_private_key", default=None,
468 help="point to the private key file to use if not yet installed in sfi_dir")
469 parser.add_option("-t", "--timeout", dest="timeout", default=None,
470 help="Amout of time to wait before timing out the request")
471 parser.add_option("-?", "--commands",
472 action="store_true", dest="command_help", default=False,
473 help="one page summary on commands & exit")
474 parser.disable_interspersed_args()
479 def print_help (self):
480 print "==================== Generic sfi usage"
481 self.sfi_parser.print_help()
482 print "==================== Specific command usage"
483 self.command_parser.print_help()
486 # Main: parse arguments and dispatch to command
488 def dispatch(self, command, command_options, command_args):
489 return getattr(self, command)(command_options, command_args)
492 self.sfi_parser = self.create_parser()
493 (options, args) = self.sfi_parser.parse_args()
494 if options.command_help:
495 self.print_command_help(options)
497 self.options = options
499 self.logger.setLevelFromOptVerbose(self.options.verbose)
502 self.logger.critical("No command given. Use -h for help.")
503 self.print_command_help(options)
506 # complete / find unique match with command set
507 command_candidates = Candidates (self.available_names)
509 command = command_candidates.only_match(input)
511 self.print_command_help(options)
513 # second pass options parsing
514 self.command_parser = self.create_command_parser(command)
515 (command_options, command_args) = self.command_parser.parse_args(args[1:])
516 self.command_options = command_options
520 self.logger.info("Command=%s" % command)
523 self.dispatch(command, command_options, command_args)
525 self.logger.critical ("Unknown command %s"%command)
531 def read_config(self):
532 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
534 config = Config (config_file)
536 self.logger.critical("Failed to read configuration file %s"%config_file)
537 self.logger.info("Make sure to remove the export clauses and to add quotes")
538 if self.options.verbose==0:
539 self.logger.info("Re-run with -v for more details")
541 self.logger.log_exc("Could not read config file %s"%config_file)
546 if (self.options.sm is not None):
547 self.sm_url = self.options.sm
548 elif hasattr(config, "SFI_SM"):
549 self.sm_url = config.SFI_SM
551 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
555 if (self.options.registry is not None):
556 self.reg_url = self.options.registry
557 elif hasattr(config, "SFI_REGISTRY"):
558 self.reg_url = config.SFI_REGISTRY
560 self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
564 if (self.options.user is not None):
565 self.user = self.options.user
566 elif hasattr(config, "SFI_USER"):
567 self.user = config.SFI_USER
569 self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
573 if (self.options.auth is not None):
574 self.authority = self.options.auth
575 elif hasattr(config, "SFI_AUTH"):
576 self.authority = config.SFI_AUTH
578 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
581 self.config_file=config_file
585 def show_config (self):
586 print "From configuration file %s"%self.config_file
589 ('SFI_AUTH','authority'),
591 ('SFI_REGISTRY','reg_url'),
593 for (external_name, internal_name) in flags:
594 print "%s='%s'"%(external_name,getattr(self,internal_name))
597 # Get various credential and spec files
599 # Establishes limiting conventions
600 # - conflates MAs and SAs
601 # - assumes last token in slice name is unique
603 # Bootstraps credentials
604 # - bootstrap user credential from self-signed certificate
605 # - bootstrap authority credential from user credential
606 # - bootstrap slice credential from user credential
609 # init self-signed cert, user credentials and gid
610 def bootstrap (self):
611 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
612 # if -k is provided, use this to initialize private key
613 if self.options.user_private_key:
614 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
616 # trigger legacy compat code if needed
617 # the name has changed from just <leaf>.pkey to <hrn>.pkey
618 if not os.path.isfile(client_bootstrap.private_key_filename()):
619 self.logger.info ("private key not found, trying legacy name")
621 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
622 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
623 client_bootstrap.init_private_key_if_missing (legacy_private_key)
624 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
626 self.logger.log_exc("Can't find private key ")
630 client_bootstrap.bootstrap_my_gid()
631 # extract what's needed
632 self.private_key = client_bootstrap.private_key()
633 self.my_credential_string = client_bootstrap.my_credential_string ()
634 self.my_gid = client_bootstrap.my_gid ()
635 self.client_bootstrap = client_bootstrap
638 def my_authority_credential_string(self):
639 if not self.authority:
640 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
642 return self.client_bootstrap.authority_credential_string (self.authority)
644 def slice_credential_string(self, name):
645 return self.client_bootstrap.slice_credential_string (name)
647 # xxx should be supported by sfaclientbootstrap as well
648 def delegate_cred(self, object_cred, hrn, type='authority'):
649 # the gid and hrn of the object we are delegating
650 if isinstance(object_cred, str):
651 object_cred = Credential(string=object_cred)
652 object_gid = object_cred.get_gid_object()
653 object_hrn = object_gid.get_hrn()
655 if not object_cred.get_privileges().get_all_delegate():
656 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
659 # the delegating user's gid
660 caller_gidfile = self.my_gid()
662 # the gid of the user who will be delegated to
663 delegee_gid = self.client_bootstrap.gid(hrn,type)
664 delegee_hrn = delegee_gid.get_hrn()
665 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
666 return dcred.save_to_string(save_parents=True)
669 # Management of the servers
674 if not hasattr (self, 'registry_proxy'):
675 self.logger.info("Contacting Registry at: %s"%self.reg_url)
676 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
677 timeout=self.options.timeout, verbose=self.options.debug)
678 return self.registry_proxy
682 if not hasattr (self, 'sliceapi_proxy'):
683 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
684 if hasattr(self.command_options,'component') and self.command_options.component:
685 # resolve the hrn at the registry
686 node_hrn = self.command_options.component
687 records = self.registry().Resolve(node_hrn, self.my_credential_string)
688 records = filter_records('node', records)
690 self.logger.warning("No such component:%r"% opts.component)
692 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
693 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
695 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
696 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
697 self.sm_url = 'http://' + self.sm_url
698 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
699 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
700 timeout=self.options.timeout, verbose=self.options.debug)
701 return self.sliceapi_proxy
703 def get_cached_server_version(self, server):
704 # check local cache first
707 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
708 cache_key = server.url + "-version"
710 cache = Cache(cache_file)
713 self.logger.info("Local cache not found at: %s" % cache_file)
716 version = cache.get(cache_key)
719 result = server.GetVersion()
720 version= ReturnValue.get_value(result)
721 # cache version for 20 minutes
722 cache.add(cache_key, version, ttl= 60*20)
723 self.logger.info("Updating cache file %s" % cache_file)
724 cache.save_to_file(cache_file)
728 ### resurrect this temporarily so we can support V1 aggregates for a while
729 def server_supports_options_arg(self, server):
731 Returns true if server support the optional call_id arg, false otherwise.
733 server_version = self.get_cached_server_version(server)
735 # xxx need to rewrite this
736 if int(server_version.get('geni_api')) >= 2:
740 def server_supports_call_id_arg(self, server):
741 server_version = self.get_cached_server_version(server)
743 if 'sfa' in server_version and 'code_tag' in server_version:
744 code_tag = server_version['code_tag']
745 code_tag_parts = code_tag.split("-")
746 version_parts = code_tag_parts[0].split(".")
747 major, minor = version_parts[0], version_parts[1]
748 rev = code_tag_parts[1]
749 if int(major) == 1 and minor == 0 and build >= 22:
753 ### ois = options if supported
754 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
755 def ois (self, server, option_dict):
756 if self.server_supports_options_arg (server):
758 elif self.server_supports_call_id_arg (server):
759 return [ unique_call_id () ]
763 ### cis = call_id if supported - like ois
764 def cis (self, server):
765 if self.server_supports_call_id_arg (server):
766 return [ unique_call_id ]
770 ######################################## miscell utilities
771 def get_rspec_file(self, rspec):
772 if (os.path.isabs(rspec)):
775 file = os.path.join(self.options.sfi_dir, rspec)
776 if (os.path.isfile(file)):
779 self.logger.critical("No such rspec file %s"%rspec)
782 def get_record_file(self, record):
783 if (os.path.isabs(record)):
786 file = os.path.join(self.options.sfi_dir, record)
787 if (os.path.isfile(file)):
790 self.logger.critical("No such registry record file %s"%record)
794 #==========================================================================
795 # Following functions implement the commands
797 # Registry-related commands
798 #==========================================================================
800 def version(self, options, args):
802 display an SFA server version (GetVersion)
803 or version information about sfi itself
805 if options.version_local:
806 version=version_core()
808 if options.version_registry:
809 server=self.registry()
811 server = self.sliceapi()
812 result = server.GetVersion()
813 version = ReturnValue.get_value(result)
815 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
817 pprinter = PrettyPrinter(indent=4)
818 pprinter.pprint(version)
820 def list(self, options, args):
822 list entries in named authority registry (List)
829 if options.recursive:
830 opts['recursive'] = options.recursive
832 if options.show_credential:
833 show_credentials(self.my_credential_string)
835 list = self.registry().List(hrn, self.my_credential_string, options)
837 raise Exception, "Not enough parameters for the 'list' command"
839 # filter on person, slice, site, node, etc.
840 # THis really should be in the self.filter_records funct def comment...
841 list = filter_records(options.type, list)
843 print "%s (%s)" % (record['hrn'], record['type'])
845 save_records_to_file(options.file, list, options.fileformat)
848 def show(self, options, args):
850 show details about named registry record (Resolve)
856 record_dicts = self.registry().Resolve(hrn, self.my_credential_string)
857 record_dicts = filter_records(options.type, record_dicts)
859 self.logger.error("No record of type %s"% options.type)
861 # user has required to focus on some keys
863 def project (record):
865 for key in options.keys:
866 try: projected[key]=record[key]
869 record_dicts = [ project (record) for record in record_dicts ]
870 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
871 for record in records:
872 if (options.format == "text"): record.dump(sort=True)
873 else: print record.save_as_xml()
875 save_records_to_file(options.file, record_dicts, options.fileformat)
878 def add(self, options, args):
879 "add record into registry from xml file (Register)"
880 auth_cred = self.my_authority_credential_string()
881 if options.show_credential:
882 show_credentials(auth_cred)
885 record_filepath = args[0]
886 rec_file = self.get_record_file(record_filepath)
887 record_dict.update(load_record_from_file(rec_file).todict())
889 record_dict.update(load_record_from_opts(options).todict())
890 # we should have a type by now
891 if 'type' not in record_dict :
894 # this is still planetlab dependent.. as plc will whine without that
895 # also, it's only for adding
896 if record_dict['type'] == 'user':
897 if not 'first_name' in record_dict:
898 record_dict['first_name'] = record_dict['hrn']
899 if 'last_name' not in record_dict:
900 record_dict['last_name'] = record_dict['hrn']
901 return self.registry().Register(record_dict, auth_cred)
903 def update(self, options, args):
904 "update record into registry from xml file (Update)"
907 record_filepath = args[0]
908 rec_file = self.get_record_file(record_filepath)
909 record_dict.update(load_record_from_file(rec_file).todict())
911 record_dict.update(load_record_from_opts(options).todict())
912 # at the very least we need 'type' here
913 if 'type' not in record_dict:
917 # don't translate into an object, as this would possibly distort
918 # user-provided data; e.g. add an 'email' field to Users
919 if record_dict['type'] == "user":
920 if record_dict['hrn'] == self.user:
921 cred = self.my_credential_string
923 cred = self.my_authority_credential_string()
924 elif record_dict['type'] in ["slice"]:
926 cred = self.slice_credential_string(record_dict['hrn'])
927 except ServerException, e:
928 # XXX smbaker -- once we have better error return codes, update this
929 # to do something better than a string compare
930 if "Permission error" in e.args[0]:
931 cred = self.my_authority_credential_string()
934 elif record_dict['type'] in ["authority"]:
935 cred = self.my_authority_credential_string()
936 elif record_dict['type'] == 'node':
937 cred = self.my_authority_credential_string()
939 raise "unknown record type" + record_dict['type']
940 if options.show_credential:
941 show_credentials(cred)
942 return self.registry().Update(record_dict, cred)
944 def remove(self, options, args):
945 "remove registry record by name (Remove)"
946 auth_cred = self.my_authority_credential_string()
954 if options.show_credential:
955 show_credentials(auth_cred)
956 return self.registry().Remove(hrn, auth_cred, type)
958 # ==================================================================
959 # Slice-related commands
960 # ==================================================================
962 def slices(self, options, args):
963 "list instantiated slices (ListSlices) - returns urn's"
964 server = self.sliceapi()
966 creds = [self.my_credential_string]
968 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
969 creds.append(delegated_cred)
970 # options and call_id when supported
972 api_options['call_id']=unique_call_id()
973 if options.show_credential:
974 show_credentials(creds)
975 result = server.ListSlices(creds, *self.ois(server,api_options))
976 value = ReturnValue.get_value(result)
978 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
983 # show rspec for named slice
984 def resources(self, options, args):
986 with no arg, discover available resources, (ListResources)
987 or with an slice hrn, shows currently provisioned resources
989 server = self.sliceapi()
994 creds.append(self.slice_credential_string(args[0]))
996 creds.append(self.my_credential_string)
998 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
999 if options.show_credential:
1000 show_credentials(creds)
1002 # no need to check if server accepts the options argument since the options has
1003 # been a required argument since v1 API
1005 # always send call_id to v2 servers
1006 api_options ['call_id'] = unique_call_id()
1007 # ask for cached value if available
1008 api_options ['cached'] = True
1011 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
1013 api_options['info'] = options.info
1014 if options.list_leases:
1015 api_options['list_leases'] = options.list_leases
1017 if options.current == True:
1018 api_options['cached'] = False
1020 api_options['cached'] = True
1021 if options.rspec_version:
1022 version_manager = VersionManager()
1023 server_version = self.get_cached_server_version(server)
1024 if 'sfa' in server_version:
1025 # just request the version the client wants
1026 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1028 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1030 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1031 result = server.ListResources (creds, api_options)
1032 value = ReturnValue.get_value(result)
1033 if self.options.raw:
1034 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1035 if options.file is not None:
1036 save_rspec_to_file(value, options.file)
1037 if (self.options.raw is None) and (options.file is None):
1038 display_rspec(value, options.format)
1042 def create(self, options, args):
1044 create or update named slice with given rspec
1046 server = self.sliceapi()
1048 # xxx do we need to check usage (len(args)) ?
1051 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1054 creds = [self.slice_credential_string(slice_hrn)]
1056 delegated_cred = None
1057 server_version = self.get_cached_server_version(server)
1058 if server_version.get('interface') == 'slicemgr':
1059 # delegate our cred to the slice manager
1060 # do not delegate cred to slicemgr...not working at the moment
1062 #if server_version.get('hrn'):
1063 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1064 #elif server_version.get('urn'):
1065 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1067 if options.show_credential:
1068 show_credentials(creds)
1071 rspec_file = self.get_rspec_file(args[1])
1072 rspec = open(rspec_file).read()
1075 # need to pass along user keys to the aggregate.
1077 # { urn: urn:publicid:IDN+emulab.net+user+alice
1078 # keys: [<ssh key A>, <ssh key B>]
1081 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1082 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1083 slice_record = slice_records[0]
1084 user_hrns = slice_record['researcher']
1085 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1086 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1088 if 'sfa' not in server_version:
1089 users = pg_users_arg(user_records)
1090 rspec = RSpec(rspec)
1091 rspec.filter({'component_manager_id': server_version['urn']})
1092 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1094 users = sfa_users_arg(user_records, slice_record)
1096 # do not append users, keys, or slice tags. Anything
1097 # not contained in this request will be removed from the slice
1099 # CreateSliver has supported the options argument for a while now so it should
1100 # be safe to assume this server support it
1102 api_options ['append'] = False
1103 api_options ['call_id'] = unique_call_id()
1104 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1105 value = ReturnValue.get_value(result)
1106 if self.options.raw:
1107 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1108 if options.file is not None:
1109 save_rspec_to_file (value, options.file)
1110 if (self.options.raw is None) and (options.file is None):
1115 def delete(self, options, args):
1117 delete named slice (DeleteSliver)
1119 server = self.sliceapi()
1123 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1126 slice_cred = self.slice_credential_string(slice_hrn)
1127 creds = [slice_cred]
1128 if options.delegate:
1129 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1130 creds.append(delegated_cred)
1132 # options and call_id when supported
1134 api_options ['call_id'] = unique_call_id()
1135 if options.show_credential:
1136 show_credentials(creds)
1137 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1138 value = ReturnValue.get_value(result)
1139 if self.options.raw:
1140 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1145 def status(self, options, args):
1147 retrieve slice status (SliverStatus)
1149 server = self.sliceapi()
1153 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1156 slice_cred = self.slice_credential_string(slice_hrn)
1157 creds = [slice_cred]
1158 if options.delegate:
1159 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1160 creds.append(delegated_cred)
1162 # options and call_id when supported
1164 api_options['call_id']=unique_call_id()
1165 if options.show_credential:
1166 show_credentials(creds)
1167 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1168 value = ReturnValue.get_value(result)
1169 if self.options.raw:
1170 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1174 def start(self, options, args):
1176 start named slice (Start)
1178 server = self.sliceapi()
1182 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1185 slice_cred = self.slice_credential_string(args[0])
1186 creds = [slice_cred]
1187 if options.delegate:
1188 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1189 creds.append(delegated_cred)
1190 # xxx Thierry - does this not need an api_options as well ?
1191 result = server.Start(slice_urn, creds)
1192 value = ReturnValue.get_value(result)
1193 if self.options.raw:
1194 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1199 def stop(self, options, args):
1201 stop named slice (Stop)
1203 server = self.sliceapi()
1206 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1208 slice_cred = self.slice_credential_string(args[0])
1209 creds = [slice_cred]
1210 if options.delegate:
1211 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1212 creds.append(delegated_cred)
1213 result = server.Stop(slice_urn, creds)
1214 value = ReturnValue.get_value(result)
1215 if self.options.raw:
1216 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1222 def reset(self, options, args):
1224 reset named slice (reset_slice)
1226 server = self.sliceapi()
1229 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1231 slice_cred = self.slice_credential_string(args[0])
1232 creds = [slice_cred]
1233 if options.delegate:
1234 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1235 creds.append(delegated_cred)
1236 result = server.reset_slice(creds, slice_urn)
1237 value = ReturnValue.get_value(result)
1238 if self.options.raw:
1239 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1244 def renew(self, options, args):
1246 renew slice (RenewSliver)
1248 server = self.sliceapi()
1252 [ slice_hrn, input_time ] = args
1254 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1255 # time: don't try to be smart on the time format, server-side will
1257 slice_cred = self.slice_credential_string(args[0])
1258 creds = [slice_cred]
1259 if options.delegate:
1260 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1261 creds.append(delegated_cred)
1262 # options and call_id when supported
1264 api_options['call_id']=unique_call_id()
1265 if options.show_credential:
1266 show_credentials(creds)
1267 result = server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
1268 value = ReturnValue.get_value(result)
1269 if self.options.raw:
1270 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1276 def shutdown(self, options, args):
1278 shutdown named slice (Shutdown)
1280 server = self.sliceapi()
1283 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1285 slice_cred = self.slice_credential_string(slice_hrn)
1286 creds = [slice_cred]
1287 if options.delegate:
1288 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1289 creds.append(delegated_cred)
1290 result = server.Shutdown(slice_urn, creds)
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 get_ticket(self, options, args):
1301 get a ticket for the specified slice
1303 server = self.sliceapi()
1305 slice_hrn, rspec_path = args[0], args[1]
1306 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1308 slice_cred = self.slice_credential_string(slice_hrn)
1309 creds = [slice_cred]
1310 if options.delegate:
1311 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1312 creds.append(delegated_cred)
1314 rspec_file = self.get_rspec_file(rspec_path)
1315 rspec = open(rspec_file).read()
1316 # options and call_id when supported
1318 api_options['call_id']=unique_call_id()
1319 # get ticket at the server
1320 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1322 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1323 self.logger.info("writing ticket to %s"%file)
1324 ticket = SfaTicket(string=ticket_string)
1325 ticket.save_to_file(filename=file, save_parents=True)
1327 def redeem_ticket(self, options, args):
1329 Connects to nodes in a slice and redeems a ticket
1330 (slice hrn is retrieved from the ticket)
1332 ticket_file = args[0]
1334 # get slice hrn from the ticket
1335 # use this to get the right slice credential
1336 ticket = SfaTicket(filename=ticket_file)
1338 ticket_string = ticket.save_to_string(save_parents=True)
1340 slice_hrn = ticket.gidObject.get_hrn()
1341 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1342 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1343 slice_cred = self.slice_credential_string(slice_hrn)
1345 # get a list of node hostnames from the RSpec
1346 tree = etree.parse(StringIO(ticket.rspec))
1347 root = tree.getroot()
1348 hostnames = root.xpath("./network/site/node/hostname/text()")
1350 # create an xmlrpc connection to the component manager at each of these
1351 # components and gall redeem_ticket
1353 for hostname in hostnames:
1355 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1356 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1357 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1358 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1359 timeout=self.options.timeout, verbose=self.options.debug)
1360 server.RedeemTicket(ticket_string, slice_cred)
1361 self.logger.info("Success")
1362 except socket.gaierror:
1363 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1364 except Exception, e:
1365 self.logger.log_exc(e.message)
1368 def create_gid(self, options, args):
1370 Create a GID (CreateGid)
1375 target_hrn = args[0]
1376 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1378 filename = options.file
1380 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1381 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1382 GID(string=gid).save_to_file(filename)
1385 def delegate(self, options, args):
1387 (locally) create delegate credential for use by given hrn
1389 delegee_hrn = args[0]
1390 if options.delegate_user:
1391 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1392 elif options.delegate_slice:
1393 slice_cred = self.slice_credential_string(options.delegate_slice)
1394 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1396 self.logger.warning("Must specify either --user or --slice <hrn>")
1398 delegated_cred = Credential(string=cred)
1399 object_hrn = delegated_cred.get_gid_object().get_hrn()
1400 if options.delegate_user:
1401 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1402 + get_leaf(object_hrn) + ".cred")
1403 elif options.delegate_slice:
1404 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1405 + get_leaf(object_hrn) + ".cred")
1407 delegated_cred.save_to_file(dest_fn, save_parents=True)
1409 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1411 def get_trusted_certs(self, options, args):
1413 return uhe trusted certs at this interface (get_trusted_certs)
1415 trusted_certs = self.registry().get_trusted_certs()
1416 for trusted_cert in trusted_certs:
1417 gid = GID(string=trusted_cert)
1419 cert = Certificate(string=trusted_cert)
1420 self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
1423 def config (self, options, args):
1424 "Display contents of current config"