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"),
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", "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.debug("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,
613 # if -k is provided, use this to initialize private key
614 if self.options.user_private_key:
615 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
617 # trigger legacy compat code if needed
618 # the name has changed from just <leaf>.pkey to <hrn>.pkey
619 if not os.path.isfile(client_bootstrap.private_key_filename()):
620 self.logger.info ("private key not found, trying legacy name")
622 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
623 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
624 client_bootstrap.init_private_key_if_missing (legacy_private_key)
625 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
627 self.logger.log_exc("Can't find private key ")
631 client_bootstrap.bootstrap_my_gid()
632 # extract what's needed
633 self.private_key = client_bootstrap.private_key()
634 self.my_credential_string = client_bootstrap.my_credential_string ()
635 self.my_gid = client_bootstrap.my_gid ()
636 self.client_bootstrap = client_bootstrap
639 def my_authority_credential_string(self):
640 if not self.authority:
641 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
643 return self.client_bootstrap.authority_credential_string (self.authority)
645 def slice_credential_string(self, name):
646 return self.client_bootstrap.slice_credential_string (name)
648 # xxx should be supported by sfaclientbootstrap as well
649 def delegate_cred(self, object_cred, hrn, type='authority'):
650 # the gid and hrn of the object we are delegating
651 if isinstance(object_cred, str):
652 object_cred = Credential(string=object_cred)
653 object_gid = object_cred.get_gid_object()
654 object_hrn = object_gid.get_hrn()
656 if not object_cred.get_privileges().get_all_delegate():
657 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
660 # the delegating user's gid
661 caller_gidfile = self.my_gid()
663 # the gid of the user who will be delegated to
664 delegee_gid = self.client_bootstrap.gid(hrn,type)
665 delegee_hrn = delegee_gid.get_hrn()
666 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
667 return dcred.save_to_string(save_parents=True)
670 # Management of the servers
675 if not hasattr (self, 'registry_proxy'):
676 self.logger.info("Contacting Registry at: %s"%self.reg_url)
677 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
678 timeout=self.options.timeout, verbose=self.options.debug)
679 return self.registry_proxy
683 if not hasattr (self, 'sliceapi_proxy'):
684 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
685 if hasattr(self.command_options,'component') and self.command_options.component:
686 # resolve the hrn at the registry
687 node_hrn = self.command_options.component
688 records = self.registry().Resolve(node_hrn, self.my_credential_string)
689 records = filter_records('node', records)
691 self.logger.warning("No such component:%r"% opts.component)
693 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
694 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
696 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
697 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
698 self.sm_url = 'http://' + self.sm_url
699 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
700 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
701 timeout=self.options.timeout, verbose=self.options.debug)
702 return self.sliceapi_proxy
704 def get_cached_server_version(self, server):
705 # check local cache first
708 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
709 cache_key = server.url + "-version"
711 cache = Cache(cache_file)
714 self.logger.info("Local cache not found at: %s" % cache_file)
717 version = cache.get(cache_key)
720 result = server.GetVersion()
721 version= ReturnValue.get_value(result)
722 # cache version for 20 minutes
723 cache.add(cache_key, version, ttl= 60*20)
724 self.logger.info("Updating cache file %s" % cache_file)
725 cache.save_to_file(cache_file)
729 ### resurrect this temporarily so we can support V1 aggregates for a while
730 def server_supports_options_arg(self, server):
732 Returns true if server support the optional call_id arg, false otherwise.
734 server_version = self.get_cached_server_version(server)
736 # xxx need to rewrite this
737 if int(server_version.get('geni_api')) >= 2:
741 def server_supports_call_id_arg(self, server):
742 server_version = self.get_cached_server_version(server)
744 if 'sfa' in server_version and 'code_tag' in server_version:
745 code_tag = server_version['code_tag']
746 code_tag_parts = code_tag.split("-")
747 version_parts = code_tag_parts[0].split(".")
748 major, minor = version_parts[0], version_parts[1]
749 rev = code_tag_parts[1]
750 if int(major) == 1 and minor == 0 and build >= 22:
754 ### ois = options if supported
755 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
756 def ois (self, server, option_dict):
757 if self.server_supports_options_arg (server):
759 elif self.server_supports_call_id_arg (server):
760 return [ unique_call_id () ]
764 ### cis = call_id if supported - like ois
765 def cis (self, server):
766 if self.server_supports_call_id_arg (server):
767 return [ unique_call_id ]
771 ######################################## miscell utilities
772 def get_rspec_file(self, rspec):
773 if (os.path.isabs(rspec)):
776 file = os.path.join(self.options.sfi_dir, rspec)
777 if (os.path.isfile(file)):
780 self.logger.critical("No such rspec file %s"%rspec)
783 def get_record_file(self, record):
784 if (os.path.isabs(record)):
787 file = os.path.join(self.options.sfi_dir, record)
788 if (os.path.isfile(file)):
791 self.logger.critical("No such registry record file %s"%record)
795 #==========================================================================
796 # Following functions implement the commands
798 # Registry-related commands
799 #==========================================================================
801 def version(self, options, args):
803 display an SFA server version (GetVersion)
804 or version information about sfi itself
806 if options.version_local:
807 version=version_core()
809 if options.version_registry:
810 server=self.registry()
812 server = self.sliceapi()
813 result = server.GetVersion()
814 version = ReturnValue.get_value(result)
816 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
818 pprinter = PrettyPrinter(indent=4)
819 pprinter.pprint(version)
821 def list(self, options, args):
823 list entries in named authority registry (List)
830 if options.recursive:
831 opts['recursive'] = options.recursive
833 if options.show_credential:
834 show_credentials(self.my_credential_string)
836 list = self.registry().List(hrn, self.my_credential_string, options)
838 raise Exception, "Not enough parameters for the 'list' command"
840 # filter on person, slice, site, node, etc.
841 # THis really should be in the self.filter_records funct def comment...
842 list = filter_records(options.type, list)
844 print "%s (%s)" % (record['hrn'], record['type'])
846 save_records_to_file(options.file, list, options.fileformat)
849 def show(self, options, args):
851 show details about named registry record (Resolve)
857 record_dicts = self.registry().Resolve(hrn, self.my_credential_string)
858 record_dicts = filter_records(options.type, record_dicts)
860 self.logger.error("No record of type %s"% options.type)
862 # user has required to focus on some keys
864 def project (record):
866 for key in options.keys:
867 try: projected[key]=record[key]
870 record_dicts = [ project (record) for record in record_dicts ]
871 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
872 for record in records:
873 if (options.format == "text"): record.dump(sort=True)
874 else: print record.save_as_xml()
876 save_records_to_file(options.file, record_dicts, options.fileformat)
879 def add(self, options, args):
880 "add record into registry from xml file (Register)"
881 auth_cred = self.my_authority_credential_string()
882 if options.show_credential:
883 show_credentials(auth_cred)
886 record_filepath = args[0]
887 rec_file = self.get_record_file(record_filepath)
888 record_dict.update(load_record_from_file(rec_file).todict())
890 record_dict.update(load_record_from_opts(options).todict())
891 # we should have a type by now
892 if 'type' not in record_dict :
895 # this is still planetlab dependent.. as plc will whine without that
896 # also, it's only for adding
897 if record_dict['type'] == 'user':
898 if not 'first_name' in record_dict:
899 record_dict['first_name'] = record_dict['hrn']
900 if 'last_name' not in record_dict:
901 record_dict['last_name'] = record_dict['hrn']
902 return self.registry().Register(record_dict, auth_cred)
904 def update(self, options, args):
905 "update record into registry from xml file (Update)"
908 record_filepath = args[0]
909 rec_file = self.get_record_file(record_filepath)
910 record_dict.update(load_record_from_file(rec_file).todict())
912 record_dict.update(load_record_from_opts(options).todict())
913 # at the very least we need 'type' here
914 if 'type' not in record_dict:
918 # don't translate into an object, as this would possibly distort
919 # user-provided data; e.g. add an 'email' field to Users
920 if record_dict['type'] == "user":
921 if record_dict['hrn'] == self.user:
922 cred = self.my_credential_string
924 cred = self.my_authority_credential_string()
925 elif record_dict['type'] in ["slice"]:
927 cred = self.slice_credential_string(record_dict['hrn'])
928 except ServerException, e:
929 # XXX smbaker -- once we have better error return codes, update this
930 # to do something better than a string compare
931 if "Permission error" in e.args[0]:
932 cred = self.my_authority_credential_string()
935 elif record_dict['type'] in ["authority"]:
936 cred = self.my_authority_credential_string()
937 elif record_dict['type'] == 'node':
938 cred = self.my_authority_credential_string()
940 raise "unknown record type" + record_dict['type']
941 if options.show_credential:
942 show_credentials(cred)
943 return self.registry().Update(record_dict, cred)
945 def remove(self, options, args):
946 "remove registry record by name (Remove)"
947 auth_cred = self.my_authority_credential_string()
955 if options.show_credential:
956 show_credentials(auth_cred)
957 return self.registry().Remove(hrn, auth_cred, type)
959 # ==================================================================
960 # Slice-related commands
961 # ==================================================================
963 def slices(self, options, args):
964 "list instantiated slices (ListSlices) - returns urn's"
965 server = self.sliceapi()
967 creds = [self.my_credential_string]
969 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
970 creds.append(delegated_cred)
971 # options and call_id when supported
973 api_options['call_id']=unique_call_id()
974 if options.show_credential:
975 show_credentials(creds)
976 result = server.ListSlices(creds, *self.ois(server,api_options))
977 value = ReturnValue.get_value(result)
979 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
984 # show rspec for named slice
985 def resources(self, options, args):
987 with no arg, discover available resources, (ListResources)
988 or with an slice hrn, shows currently provisioned resources
990 server = self.sliceapi()
995 creds.append(self.slice_credential_string(args[0]))
997 creds.append(self.my_credential_string)
999 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1000 if options.show_credential:
1001 show_credentials(creds)
1003 # no need to check if server accepts the options argument since the options has
1004 # been a required argument since v1 API
1006 # always send call_id to v2 servers
1007 api_options ['call_id'] = unique_call_id()
1008 # ask for cached value if available
1009 api_options ['cached'] = True
1012 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
1014 api_options['info'] = options.info
1015 if options.list_leases:
1016 api_options['list_leases'] = options.list_leases
1018 if options.current == True:
1019 api_options['cached'] = False
1021 api_options['cached'] = True
1022 if options.rspec_version:
1023 version_manager = VersionManager()
1024 server_version = self.get_cached_server_version(server)
1025 if 'sfa' in server_version:
1026 # just request the version the client wants
1027 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1029 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1031 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1032 result = server.ListResources (creds, api_options)
1033 value = ReturnValue.get_value(result)
1034 if self.options.raw:
1035 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1036 if options.file is not None:
1037 save_rspec_to_file(value, options.file)
1038 if (self.options.raw is None) and (options.file is None):
1039 display_rspec(value, options.format)
1043 def create(self, options, args):
1045 create or update named slice with given rspec
1047 server = self.sliceapi()
1049 # xxx do we need to check usage (len(args)) ?
1052 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1055 creds = [self.slice_credential_string(slice_hrn)]
1057 delegated_cred = None
1058 server_version = self.get_cached_server_version(server)
1059 if server_version.get('interface') == 'slicemgr':
1060 # delegate our cred to the slice manager
1061 # do not delegate cred to slicemgr...not working at the moment
1063 #if server_version.get('hrn'):
1064 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1065 #elif server_version.get('urn'):
1066 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1068 if options.show_credential:
1069 show_credentials(creds)
1072 rspec_file = self.get_rspec_file(args[1])
1073 rspec = open(rspec_file).read()
1076 # need to pass along user keys to the aggregate.
1078 # { urn: urn:publicid:IDN+emulab.net+user+alice
1079 # keys: [<ssh key A>, <ssh key B>]
1082 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1083 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1084 slice_record = slice_records[0]
1085 user_hrns = slice_record['researcher']
1086 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1087 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1089 if 'sfa' not in server_version:
1090 users = pg_users_arg(user_records)
1091 rspec = RSpec(rspec)
1092 rspec.filter({'component_manager_id': server_version['urn']})
1093 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1095 print >>sys.stderr, "\r\n \r\n \r\n WOOOOOO"
1096 users = sfa_users_arg(user_records, slice_record)
1098 # do not append users, keys, or slice tags. Anything
1099 # not contained in this request will be removed from the slice
1101 # CreateSliver has supported the options argument for a while now so it should
1102 # be safe to assume this server support it
1104 api_options ['append'] = False
1105 api_options ['call_id'] = unique_call_id()
1106 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1107 value = ReturnValue.get_value(result)
1108 if self.options.raw:
1109 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1110 if options.file is not None:
1111 save_rspec_to_file (value, options.file)
1112 if (self.options.raw is None) and (options.file is None):
1117 def delete(self, options, args):
1119 delete named slice (DeleteSliver)
1121 server = self.sliceapi()
1125 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1128 slice_cred = self.slice_credential_string(slice_hrn)
1129 creds = [slice_cred]
1130 if options.delegate:
1131 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1132 creds.append(delegated_cred)
1134 # options and call_id when supported
1136 api_options ['call_id'] = unique_call_id()
1137 if options.show_credential:
1138 show_credentials(creds)
1139 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1140 value = ReturnValue.get_value(result)
1141 if self.options.raw:
1142 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1147 def status(self, options, args):
1149 retrieve slice status (SliverStatus)
1151 server = self.sliceapi()
1155 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1158 slice_cred = self.slice_credential_string(slice_hrn)
1159 creds = [slice_cred]
1160 if options.delegate:
1161 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1162 creds.append(delegated_cred)
1164 # options and call_id when supported
1166 api_options['call_id']=unique_call_id()
1167 if options.show_credential:
1168 show_credentials(creds)
1169 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1170 value = ReturnValue.get_value(result)
1171 if self.options.raw:
1172 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1176 def start(self, options, args):
1178 start named slice (Start)
1180 server = self.sliceapi()
1184 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1187 slice_cred = self.slice_credential_string(args[0])
1188 creds = [slice_cred]
1189 if options.delegate:
1190 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1191 creds.append(delegated_cred)
1192 # xxx Thierry - does this not need an api_options as well ?
1193 result = server.Start(slice_urn, creds)
1194 value = ReturnValue.get_value(result)
1195 if self.options.raw:
1196 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1201 def stop(self, options, args):
1203 stop named slice (Stop)
1205 server = self.sliceapi()
1208 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1210 slice_cred = self.slice_credential_string(args[0])
1211 creds = [slice_cred]
1212 if options.delegate:
1213 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1214 creds.append(delegated_cred)
1215 result = server.Stop(slice_urn, creds)
1216 value = ReturnValue.get_value(result)
1217 if self.options.raw:
1218 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1224 def reset(self, options, args):
1226 reset named slice (reset_slice)
1228 server = self.sliceapi()
1231 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1233 slice_cred = self.slice_credential_string(args[0])
1234 creds = [slice_cred]
1235 if options.delegate:
1236 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1237 creds.append(delegated_cred)
1238 result = server.reset_slice(creds, slice_urn)
1239 value = ReturnValue.get_value(result)
1240 if self.options.raw:
1241 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1246 def renew(self, options, args):
1248 renew slice (RenewSliver)
1250 server = self.sliceapi()
1254 [ slice_hrn, input_time ] = args
1256 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1257 # time: don't try to be smart on the time format, server-side will
1259 slice_cred = self.slice_credential_string(args[0])
1260 creds = [slice_cred]
1261 if options.delegate:
1262 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1263 creds.append(delegated_cred)
1264 # options and call_id when supported
1266 api_options['call_id']=unique_call_id()
1267 if options.show_credential:
1268 show_credentials(creds)
1269 result = server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
1270 value = ReturnValue.get_value(result)
1271 if self.options.raw:
1272 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1278 def shutdown(self, options, args):
1280 shutdown named slice (Shutdown)
1282 server = self.sliceapi()
1285 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1287 slice_cred = self.slice_credential_string(slice_hrn)
1288 creds = [slice_cred]
1289 if options.delegate:
1290 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1291 creds.append(delegated_cred)
1292 result = server.Shutdown(slice_urn, creds)
1293 value = ReturnValue.get_value(result)
1294 if self.options.raw:
1295 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1301 def get_ticket(self, options, args):
1303 get a ticket for the specified slice
1305 server = self.sliceapi()
1307 slice_hrn, rspec_path = args[0], args[1]
1308 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1310 slice_cred = self.slice_credential_string(slice_hrn)
1311 creds = [slice_cred]
1312 if options.delegate:
1313 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1314 creds.append(delegated_cred)
1316 rspec_file = self.get_rspec_file(rspec_path)
1317 rspec = open(rspec_file).read()
1318 # options and call_id when supported
1320 api_options['call_id']=unique_call_id()
1321 # get ticket at the server
1322 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1324 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1325 self.logger.info("writing ticket to %s"%file)
1326 ticket = SfaTicket(string=ticket_string)
1327 ticket.save_to_file(filename=file, save_parents=True)
1329 def redeem_ticket(self, options, args):
1331 Connects to nodes in a slice and redeems a ticket
1332 (slice hrn is retrieved from the ticket)
1334 ticket_file = args[0]
1336 # get slice hrn from the ticket
1337 # use this to get the right slice credential
1338 ticket = SfaTicket(filename=ticket_file)
1340 ticket_string = ticket.save_to_string(save_parents=True)
1342 slice_hrn = ticket.gidObject.get_hrn()
1343 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1344 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1345 slice_cred = self.slice_credential_string(slice_hrn)
1347 # get a list of node hostnames from the RSpec
1348 tree = etree.parse(StringIO(ticket.rspec))
1349 root = tree.getroot()
1350 hostnames = root.xpath("./network/site/node/hostname/text()")
1352 # create an xmlrpc connection to the component manager at each of these
1353 # components and gall redeem_ticket
1355 for hostname in hostnames:
1357 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1358 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1359 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1360 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1361 timeout=self.options.timeout, verbose=self.options.debug)
1362 server.RedeemTicket(ticket_string, slice_cred)
1363 self.logger.info("Success")
1364 except socket.gaierror:
1365 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1366 except Exception, e:
1367 self.logger.log_exc(e.message)
1370 def gid(self, options, args):
1372 Create a GID (CreateGid)
1377 target_hrn = args[0]
1378 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1380 filename = options.file
1382 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1383 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1384 GID(string=gid).save_to_file(filename)
1387 def delegate(self, options, args):
1389 (locally) create delegate credential for use by given hrn
1391 delegee_hrn = args[0]
1392 if options.delegate_user:
1393 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1394 elif options.delegate_slice:
1395 slice_cred = self.slice_credential_string(options.delegate_slice)
1396 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1398 self.logger.warning("Must specify either --user or --slice <hrn>")
1400 delegated_cred = Credential(string=cred)
1401 object_hrn = delegated_cred.get_gid_object().get_hrn()
1402 if options.delegate_user:
1403 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1404 + get_leaf(object_hrn) + ".cred")
1405 elif options.delegate_slice:
1406 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1407 + get_leaf(object_hrn) + ".cred")
1409 delegated_cred.save_to_file(dest_fn, save_parents=True)
1411 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1413 def trusted(self, options, args):
1415 return uhe trusted certs at this interface (get_trusted_certs)
1417 trusted_certs = self.registry().get_trusted_certs()
1418 for trusted_cert in trusted_certs:
1419 gid = GID(string=trusted_cert)
1421 cert = Certificate(string=trusted_cert)
1422 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1425 def config (self, options, args):
1426 "Display contents of current config"