2 # sfi.py - basic SFA command-line client
3 # the actual binary in sfa/clientbin essentially runs main()
4 # this module is used in sfascan
17 from lxml import etree
18 from StringIO import StringIO
19 from optparse import OptionParser
20 from pprint import PrettyPrinter
22 from sfa.trust.certificate import Keypair, Certificate
23 from sfa.trust.gid import GID
24 from sfa.trust.credential import Credential
25 from sfa.trust.sfaticket import SfaTicket
27 from sfa.util.faults import SfaInvalidArgument
28 from sfa.util.sfalogging import sfi_logger
29 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
30 from sfa.util.config import Config
31 from sfa.util.version import version_core
32 from sfa.util.cache import Cache
34 from sfa.storage.record import Record
36 from sfa.rspecs.rspec import RSpec
37 from sfa.rspecs.rspec_converter import RSpecConverter
38 from sfa.rspecs.version_manager import VersionManager
40 from sfa.client.sfaclientlib import SfaClientBootstrap
41 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
42 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
43 from sfa.client.return_value import ReturnValue
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):
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
117 def save_raw_to_file(var, filename, format="text", banner=None):
119 # if filename is "-", send it to stdout
122 f = open(filename, "w")
127 elif format == "pickled":
128 f.write(pickle.dumps(var))
129 elif format == "json":
130 if hasattr(json, "dumps"):
131 f.write(json.dumps(var)) # python 2.6
133 f.write(json.write(var)) # python 2.5
135 # this should never happen
136 print "unknown output format", format
138 f.write('\n'+banner+"\n")
140 def save_rspec_to_file(rspec, filename):
141 if not filename.endswith(".rspec"):
142 filename = filename + ".rspec"
143 f = open(filename, 'w')
148 def save_records_to_file(filename, record_dicts, format="xml"):
151 for record_dict in record_dicts:
153 save_record_to_file(filename + "." + str(index), record_dict)
155 save_record_to_file(filename, record_dict)
157 elif format == "xmllist":
158 f = open(filename, "w")
159 f.write("<recordlist>\n")
160 for record_dict in record_dicts:
161 record_obj=Record(dict=record_dict)
162 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
163 f.write("</recordlist>\n")
165 elif format == "hrnlist":
166 f = open(filename, "w")
167 for record_dict in record_dicts:
168 record_obj=Record(dict=record_dict)
169 f.write(record_obj.hrn + "\n")
172 # this should never happen
173 print "unknown output format", format
175 def save_record_to_file(filename, record_dict):
176 record = Record(dict=record_dict)
177 xml = record.save_as_xml()
178 f=codecs.open(filename, encoding='utf-8',mode="w")
183 # minimally check a key argument
184 def check_ssh_key (key):
185 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
186 return re.match(good_ssh_key, key, re.IGNORECASE)
189 def load_record_from_opts(options):
191 if hasattr(options, 'xrn') and options.xrn:
192 if hasattr(options, 'type') and options.type:
193 xrn = Xrn(options.xrn, options.type)
195 xrn = Xrn(options.xrn)
196 record_dict['urn'] = xrn.get_urn()
197 record_dict['hrn'] = xrn.get_hrn()
198 record_dict['type'] = xrn.get_type()
199 if hasattr(options, 'key') and options.key:
201 pubkey = open(options.key, 'r').read()
204 if not check_ssh_key (pubkey):
205 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
206 record_dict['keys'] = [pubkey]
207 if hasattr(options, 'slices') and options.slices:
208 record_dict['slices'] = options.slices
209 if hasattr(options, 'researchers') and options.researchers:
210 record_dict['researcher'] = options.researchers
211 if hasattr(options, 'email') and options.email:
212 record_dict['email'] = options.email
213 if hasattr(options, 'pis') and options.pis:
214 record_dict['pi'] = options.pis
216 # handle extra settings
217 record_dict.update(options.extras)
219 return Record(dict=record_dict)
221 def load_record_from_file(filename):
222 f=codecs.open(filename, encoding="utf-8", mode="r")
223 xml_string = f.read()
225 return Record(xml=xml_string)
229 def unique_call_id(): return uuid.uuid4().urn
233 # dirty hack to make this class usable from the outside
234 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
237 def default_sfi_dir ():
238 if os.path.isfile("./sfi_config"):
241 return os.path.expanduser("~/.sfi/")
243 # dummy to meet Sfi's expectations for its 'options' field
244 # i.e. s/t we can do setattr on
248 def __init__ (self,options=None):
249 if options is None: options=Sfi.DummyOptions()
250 for opt in Sfi.required_options:
251 if not hasattr(options,opt): setattr(options,opt,None)
252 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
253 self.options = options
255 self.authority = None
256 self.logger = sfi_logger
257 self.logger.enable_console()
258 self.available_names = [ tuple[0] for tuple in Sfi.available ]
259 self.available_dict = dict (Sfi.available)
261 # tuples command-name expected-args in the order in which they should appear in the help
264 ("list", "authority"),
267 ("update", "record"),
270 ("resources", "[slice_hrn]"),
271 ("create", "slice_hrn rspec"),
272 ("delete", "slice_hrn"),
273 ("status", "slice_hrn"),
274 ("start", "slice_hrn"),
275 ("stop", "slice_hrn"),
276 ("reset", "slice_hrn"),
277 ("renew", "slice_hrn time"),
278 ("shutdown", "slice_hrn"),
279 ("get_ticket", "slice_hrn rspec"),
280 ("redeem_ticket", "ticket"),
281 ("delegate", "name"),
282 ("create_gid", "[name]"),
283 ("get_trusted_certs", "cred"),
287 def print_command_help (self, options):
288 verbose=getattr(options,'verbose')
289 format3="%18s %-15s %s"
292 print format3%("command","cmd_args","description")
296 self.create_parser().print_help()
297 for command in self.available_names:
298 args=self.available_dict[command]
299 method=getattr(self,command,None)
301 if method: doc=getattr(method,'__doc__',"")
302 if not doc: doc="*** no doc found ***"
303 doc=doc.strip(" \t\n")
304 doc=doc.replace("\n","\n"+35*' ')
307 print format3%(command,args,doc)
309 self.create_command_parser(command).print_help()
311 def create_command_parser(self, command):
312 if command not in self.available_dict:
313 msg="Invalid command\n"
315 msg += ','.join(self.available_names)
316 self.logger.critical(msg)
319 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
320 % (command, self.available_dict[command]))
322 if command in ("add", "update"):
323 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
324 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
325 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
326 # use --extra instead
327 # parser.add_option('-u', '--url', dest='url', metavar='<url>', default=None, help="URL, useful for slices")
328 # parser.add_option('-d', '--description', dest='description', metavar='<description>',
329 # help='Description, useful for slices', default=None)
330 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
332 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='slice xrns',
333 default='', type="str", action='callback', callback=optparse_listvalue_callback)
334 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
335 help='slice researchers', default='', type="str", action='callback',
336 callback=optparse_listvalue_callback)
337 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Principal Investigators/Project Managers',
338 default='', type="str", action='callback', callback=optparse_listvalue_callback)
339 # use --extra instead
340 # parser.add_option('-f', '--firstname', dest='firstname', metavar='<firstname>', help='user first name')
341 # parser.add_option('-l', '--lastname', dest='lastname', metavar='<lastname>', help='user last name')
342 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
343 action="callback", callback=optparse_dictvalue_callback, nargs=1,
344 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
346 # user specifies remote aggregate/sm/component
347 if command in ("resources", "slices", "create", "delete", "start", "stop",
348 "restart", "shutdown", "get_ticket", "renew", "status"):
349 parser.add_option("-d", "--delegate", dest="delegate", default=None,
351 help="Include a credential delegated to the user's root"+\
352 "authority in set of credentials for this call")
354 # registy filter option
355 if command in ("list", "show", "remove"):
356 parser.add_option("-t", "--type", dest="type", type="choice",
357 help="type filter ([all]|user|slice|authority|node|aggregate)",
358 choices=("all", "user", "slice", "authority", "node", "aggregate"),
360 if command in ("resources"):
362 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
363 help="schema type and version of resulting RSpec")
364 # disable/enable cached rspecs
365 parser.add_option("-c", "--current", dest="current", default=False,
367 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
369 parser.add_option("-f", "--format", dest="format", type="choice",
370 help="display format ([xml]|dns|ip)", default="xml",
371 choices=("xml", "dns", "ip"))
372 #panos: a new option to define the type of information about resources a user is interested in
373 parser.add_option("-i", "--info", dest="info",
374 help="optional component information", default=None)
377 # 'create' does return the new rspec, makes sense to save that too
378 if command in ("resources", "show", "list", "create_gid", 'create'):
379 parser.add_option("-o", "--output", dest="file",
380 help="output XML to file", metavar="FILE", default=None)
382 if command in ("show", "list"):
383 parser.add_option("-f", "--format", dest="format", type="choice",
384 help="display format ([text]|xml)", default="text",
385 choices=("text", "xml"))
387 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
388 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
389 choices=("xml", "xmllist", "hrnlist"))
390 if command == 'list':
391 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
392 help="list all child records", default=False)
393 if command in ("delegate"):
394 parser.add_option("-u", "--user",
395 action="store_true", dest="delegate_user", default=False,
396 help="delegate user credential")
397 parser.add_option("-s", "--slice", dest="delegate_slice",
398 help="delegate slice credential", metavar="HRN", default=None)
400 if command in ("version"):
401 parser.add_option("-R","--registry-version",
402 action="store_true", dest="version_registry", default=False,
403 help="probe registry version instead of sliceapi")
404 parser.add_option("-l","--local",
405 action="store_true", dest="version_local", default=False,
406 help="display version of the local client")
411 def create_parser(self):
413 # Generate command line parser
414 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
415 description="Commands: %s"%(" ".join(self.available_names)))
416 parser.add_option("-r", "--registry", dest="registry",
417 help="root registry", metavar="URL", default=None)
418 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
419 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
420 parser.add_option("-R", "--raw", dest="raw", default=None,
421 help="Save raw, unparsed server response to a file")
422 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
423 help="raw file format ([text]|pickled|json)", default="text",
424 choices=("text","pickled","json"))
425 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
426 help="text string to write before and after raw output")
427 parser.add_option("-d", "--dir", dest="sfi_dir",
428 help="config & working directory - default is %default",
429 metavar="PATH", default=Sfi.default_sfi_dir())
430 parser.add_option("-u", "--user", dest="user",
431 help="user name", metavar="HRN", default=None)
432 parser.add_option("-a", "--auth", dest="auth",
433 help="authority name", metavar="HRN", default=None)
434 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
435 help="verbose mode - cumulative")
436 parser.add_option("-D", "--debug",
437 action="store_true", dest="debug", default=False,
438 help="Debug (xml-rpc) protocol messages")
439 # would it make sense to use ~/.ssh/id_rsa as a default here ?
440 parser.add_option("-k", "--private-key",
441 action="store", dest="user_private_key", default=None,
442 help="point to the private key file to use if not yet installed in sfi_dir")
443 parser.add_option("-t", "--timeout", dest="timeout", default=None,
444 help="Amout of time to wait before timing out the request")
445 parser.add_option("-?", "--commands",
446 action="store_true", dest="command_help", default=False,
447 help="one page summary on commands & exit")
448 parser.disable_interspersed_args()
453 def print_help (self):
454 self.sfi_parser.print_help()
455 self.command_parser.print_help()
458 # Main: parse arguments and dispatch to command
460 def dispatch(self, command, command_options, command_args):
461 return getattr(self, command)(command_options, command_args)
464 self.sfi_parser = self.create_parser()
465 (options, args) = self.sfi_parser.parse_args()
466 if options.command_help:
467 self.print_command_help(options)
469 self.options = options
471 self.logger.setLevelFromOptVerbose(self.options.verbose)
474 self.logger.critical("No command given. Use -h for help.")
475 self.print_command_help(options)
479 self.command_parser = self.create_command_parser(command)
480 (command_options, command_args) = self.command_parser.parse_args(args[1:])
481 self.command_options = command_options
485 self.logger.info("Command=%s" % command)
488 self.dispatch(command, command_options, command_args)
490 self.logger.critical ("Unknown command %s"%command)
497 def read_config(self):
498 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
500 config = Config (config_file)
502 self.logger.critical("Failed to read configuration file %s"%config_file)
503 self.logger.info("Make sure to remove the export clauses and to add quotes")
504 if self.options.verbose==0:
505 self.logger.info("Re-run with -v for more details")
507 self.logger.log_exc("Could not read config file %s"%config_file)
512 if (self.options.sm is not None):
513 self.sm_url = self.options.sm
514 elif hasattr(config, "SFI_SM"):
515 self.sm_url = config.SFI_SM
517 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
521 if (self.options.registry is not None):
522 self.reg_url = self.options.registry
523 elif hasattr(config, "SFI_REGISTRY"):
524 self.reg_url = config.SFI_REGISTRY
526 self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
530 if (self.options.user is not None):
531 self.user = self.options.user
532 elif hasattr(config, "SFI_USER"):
533 self.user = config.SFI_USER
535 self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
539 if (self.options.auth is not None):
540 self.authority = self.options.auth
541 elif hasattr(config, "SFI_AUTH"):
542 self.authority = config.SFI_AUTH
544 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
547 self.config_file=config_file
551 def show_config (self):
552 print "From configuration file %s"%self.config_file
555 ('SFI_AUTH','authority'),
557 ('SFI_REGISTRY','reg_url'),
559 for (external_name, internal_name) in flags:
560 print "%s='%s'"%(external_name,getattr(self,internal_name))
563 # Get various credential and spec files
565 # Establishes limiting conventions
566 # - conflates MAs and SAs
567 # - assumes last token in slice name is unique
569 # Bootstraps credentials
570 # - bootstrap user credential from self-signed certificate
571 # - bootstrap authority credential from user credential
572 # - bootstrap slice credential from user credential
575 # init self-signed cert, user credentials and gid
576 def bootstrap (self):
577 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
578 # if -k is provided, use this to initialize private key
579 if self.options.user_private_key:
580 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
582 # trigger legacy compat code if needed
583 # the name has changed from just <leaf>.pkey to <hrn>.pkey
584 if not os.path.isfile(client_bootstrap.private_key_filename()):
585 self.logger.info ("private key not found, trying legacy name")
587 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
588 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
589 client_bootstrap.init_private_key_if_missing (legacy_private_key)
590 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
592 self.logger.log_exc("Can't find private key ")
596 client_bootstrap.bootstrap_my_gid()
597 # extract what's needed
598 self.private_key = client_bootstrap.private_key()
599 self.my_credential_string = client_bootstrap.my_credential_string ()
600 self.my_gid = client_bootstrap.my_gid ()
601 self.client_bootstrap = client_bootstrap
604 def my_authority_credential_string(self):
605 if not self.authority:
606 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
608 return self.client_bootstrap.authority_credential_string (self.authority)
610 def slice_credential_string(self, name):
611 return self.client_bootstrap.slice_credential_string (name)
613 # xxx should be supported by sfaclientbootstrap as well
614 def delegate_cred(self, object_cred, hrn, type='authority'):
615 # the gid and hrn of the object we are delegating
616 if isinstance(object_cred, str):
617 object_cred = Credential(string=object_cred)
618 object_gid = object_cred.get_gid_object()
619 object_hrn = object_gid.get_hrn()
621 if not object_cred.get_privileges().get_all_delegate():
622 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
625 # the delegating user's gid
626 caller_gidfile = self.my_gid()
628 # the gid of the user who will be delegated to
629 delegee_gid = self.client_bootstrap.gid(hrn,type)
630 delegee_hrn = delegee_gid.get_hrn()
631 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
632 return dcred.save_to_string(save_parents=True)
635 # Management of the servers
640 if not hasattr (self, 'registry_proxy'):
641 self.logger.info("Contacting Registry at: %s"%self.reg_url)
642 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
643 timeout=self.options.timeout, verbose=self.options.debug)
644 return self.registry_proxy
648 if not hasattr (self, 'sliceapi_proxy'):
649 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
650 if hasattr(self.command_options,'component') and self.command_options.component:
651 # resolve the hrn at the registry
652 node_hrn = self.command_options.component
653 records = self.registry().Resolve(node_hrn, self.my_credential_string)
654 records = filter_records('node', records)
656 self.logger.warning("No such component:%r"% opts.component)
658 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
659 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
661 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
662 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
663 self.sm_url = 'http://' + self.sm_url
664 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
665 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
666 timeout=self.options.timeout, verbose=self.options.debug)
667 return self.sliceapi_proxy
669 def get_cached_server_version(self, server):
670 # check local cache first
673 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
674 cache_key = server.url + "-version"
676 cache = Cache(cache_file)
679 self.logger.info("Local cache not found at: %s" % cache_file)
682 version = cache.get(cache_key)
685 result = server.GetVersion()
686 version= ReturnValue.get_value(result)
687 # cache version for 20 minutes
688 cache.add(cache_key, version, ttl= 60*20)
689 self.logger.info("Updating cache file %s" % cache_file)
690 cache.save_to_file(cache_file)
694 ### resurrect this temporarily so we can support V1 aggregates for a while
695 def server_supports_options_arg(self, server):
697 Returns true if server support the optional call_id arg, false otherwise.
699 server_version = self.get_cached_server_version(server)
701 # xxx need to rewrite this
702 if int(server_version.get('geni_api')) >= 2:
706 def server_supports_call_id_arg(self, server):
707 server_version = self.get_cached_server_version(server)
709 if 'sfa' in server_version and 'code_tag' in server_version:
710 code_tag = server_version['code_tag']
711 code_tag_parts = code_tag.split("-")
712 version_parts = code_tag_parts[0].split(".")
713 major, minor = version_parts[0], version_parts[1]
714 rev = code_tag_parts[1]
715 if int(major) == 1 and minor == 0 and build >= 22:
719 ### ois = options if supported
720 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
721 def ois (self, server, option_dict):
722 if self.server_supports_options_arg (server):
724 elif self.server_supports_call_id_arg (server):
725 return [ unique_call_id () ]
729 ### cis = call_id if supported - like ois
730 def cis (self, server):
731 if self.server_supports_call_id_arg (server):
732 return [ unique_call_id ]
736 ######################################## miscell utilities
737 def get_rspec_file(self, rspec):
738 if (os.path.isabs(rspec)):
741 file = os.path.join(self.options.sfi_dir, rspec)
742 if (os.path.isfile(file)):
745 self.logger.critical("No such rspec file %s"%rspec)
748 def get_record_file(self, record):
749 if (os.path.isabs(record)):
752 file = os.path.join(self.options.sfi_dir, record)
753 if (os.path.isfile(file)):
756 self.logger.critical("No such registry record file %s"%record)
760 #==========================================================================
761 # Following functions implement the commands
763 # Registry-related commands
764 #==========================================================================
766 def version(self, options, args):
768 display an SFA server version (GetVersion)
769 or version information about sfi itself
771 if options.version_local:
772 version=version_core()
774 if options.version_registry:
775 server=self.registry()
777 server = self.sliceapi()
778 result = server.GetVersion()
779 version = ReturnValue.get_value(result)
781 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
783 pprinter = PrettyPrinter(indent=4)
784 pprinter.pprint(version)
786 def list(self, options, args):
788 list entries in named authority registry (List)
795 if options.recursive:
796 opts['recursive'] = options.recursive
799 list = self.registry().List(hrn, self.my_credential_string, options)
801 raise Exception, "Not enough parameters for the 'list' command"
803 # filter on person, slice, site, node, etc.
804 # THis really should be in the self.filter_records funct def comment...
805 list = filter_records(options.type, list)
807 print "%s (%s)" % (record['hrn'], record['type'])
809 save_records_to_file(options.file, list, options.fileformat)
812 def show(self, options, args):
814 show details about named registry record (Resolve)
820 record_dicts = self.registry().Resolve(hrn, self.my_credential_string)
821 record_dicts = filter_records(options.type, record_dicts)
823 self.logger.error("No record of type %s"% options.type)
824 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
825 for record in records:
826 if (options.format == "text"): record.dump()
827 else: print record.save_as_xml()
829 save_records_to_file(options.file, record_dicts, options.fileformat)
832 def add(self, options, args):
833 "add record into registry from xml file (Register)"
834 auth_cred = self.my_authority_credential_string()
837 record_filepath = args[0]
838 rec_file = self.get_record_file(record_filepath)
839 record_dict.update(load_record_from_file(rec_file).todict())
841 record_dict.update(load_record_from_opts(options).todict())
842 print 'bkpt',record_dict
844 # we should have a type by now
845 if 'type' not in record_dict :
848 # this is still planetlab dependent.. as plc will whine without that
849 # also, it's only for adding
850 if record_dict['type'] == 'user':
851 if not 'first_name' in record_dict:
852 record_dict['first_name'] = record_dict['hrn']
853 if 'last_name' not in record_dict:
854 record_dict['last_name'] = record_dict['hrn']
855 return self.registry().Register(record_dict, auth_cred)
857 def update(self, options, args):
858 "update record into registry from xml file (Update)"
861 record_filepath = args[0]
862 rec_file = self.get_record_file(record_filepath)
863 record_dict.update(load_record_from_file(rec_file).todict())
865 record_dict.update(load_record_from_opts(options).todict())
870 record = Record(dict=record_dict)
871 if record.type == "user":
872 if record.hrn == self.user:
873 cred = self.my_credential_string
875 cred = self.my_authority_credential_string()
876 elif record.type in ["slice"]:
878 cred = self.slice_credential_string(record.hrn)
879 except ServerException, e:
880 # XXX smbaker -- once we have better error return codes, update this
881 # to do something better than a string compare
882 if "Permission error" in e.args[0]:
883 cred = self.my_authority_credential_string()
886 elif record.type in ["authority"]:
887 cred = self.my_authority_credential_string()
888 elif record.type == 'node':
889 cred = self.my_authority_credential_string()
891 raise "unknown record type" + record.type
892 record_dict = record.todict()
893 return self.registry().Update(record_dict, cred)
895 def remove(self, options, args):
896 "remove registry record by name (Remove)"
897 auth_cred = self.my_authority_credential_string()
905 return self.registry().Remove(hrn, auth_cred, type)
907 # ==================================================================
908 # Slice-related commands
909 # ==================================================================
911 def slices(self, options, args):
912 "list instantiated slices (ListSlices) - returns urn's"
913 server = self.sliceapi()
915 creds = [self.my_credential_string]
917 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
918 creds.append(delegated_cred)
919 # options and call_id when supported
921 api_options['call_id']=unique_call_id()
922 result = server.ListSlices(creds, *self.ois(server,api_options))
923 value = ReturnValue.get_value(result)
925 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
930 # show rspec for named slice
931 def resources(self, options, args):
933 with no arg, discover available resources, (ListResources)
934 or with an slice hrn, shows currently provisioned resources
936 server = self.sliceapi()
941 creds.append(self.slice_credential_string(args[0]))
943 creds.append(self.my_credential_string)
945 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
947 # no need to check if server accepts the options argument since the options has
948 # been a required argument since v1 API
950 # always send call_id to v2 servers
951 api_options ['call_id'] = unique_call_id()
952 # ask for cached value if available
953 api_options ['cached'] = True
956 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
958 api_options['info'] = options.info
960 if options.current == True:
961 api_options['cached'] = False
963 api_options['cached'] = True
964 if options.rspec_version:
965 version_manager = VersionManager()
966 server_version = self.get_cached_server_version(server)
967 if 'sfa' in server_version:
968 # just request the version the client wants
969 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
971 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
973 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
974 result = server.ListResources (creds, api_options)
975 value = ReturnValue.get_value(result)
977 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
978 if options.file is not None:
979 save_rspec_to_file(value, options.file)
980 if (self.options.raw is None) and (options.file is None):
981 display_rspec(value, options.format)
985 def create(self, options, args):
987 create or update named slice with given rspec
989 server = self.sliceapi()
991 # xxx do we need to check usage (len(args)) ?
994 slice_urn = hrn_to_urn(slice_hrn, 'slice')
997 creds = [self.slice_credential_string(slice_hrn)]
998 delegated_cred = None
999 server_version = self.get_cached_server_version(server)
1000 if server_version.get('interface') == 'slicemgr':
1001 # delegate our cred to the slice manager
1002 # do not delegate cred to slicemgr...not working at the moment
1004 #if server_version.get('hrn'):
1005 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1006 #elif server_version.get('urn'):
1007 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1010 rspec_file = self.get_rspec_file(args[1])
1011 rspec = open(rspec_file).read()
1014 # need to pass along user keys to the aggregate.
1016 # { urn: urn:publicid:IDN+emulab.net+user+alice
1017 # keys: [<ssh key A>, <ssh key B>]
1020 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1021 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1022 slice_record = slice_records[0]
1023 user_hrns = slice_record['researcher']
1024 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1025 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1027 if 'sfa' not in server_version:
1028 users = pg_users_arg(user_records)
1029 rspec = RSpec(rspec)
1030 rspec.filter({'component_manager_id': server_version['urn']})
1031 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1033 users = sfa_users_arg(user_records, slice_record)
1035 # do not append users, keys, or slice tags. Anything
1036 # not contained in this request will be removed from the slice
1038 # CreateSliver has supported the options argument for a while now so it should
1039 # be safe to assume this server support it
1041 api_options ['append'] = False
1042 api_options ['call_id'] = unique_call_id()
1043 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1044 value = ReturnValue.get_value(result)
1045 if self.options.raw:
1046 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1047 if options.file is not None:
1048 save_rspec_to_file (value, options.file)
1049 if (self.options.raw is None) and (options.file is None):
1054 def delete(self, options, args):
1056 delete named slice (DeleteSliver)
1058 server = self.sliceapi()
1062 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1065 slice_cred = self.slice_credential_string(slice_hrn)
1066 creds = [slice_cred]
1067 if options.delegate:
1068 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1069 creds.append(delegated_cred)
1071 # options and call_id when supported
1073 api_options ['call_id'] = unique_call_id()
1074 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1075 value = ReturnValue.get_value(result)
1076 if self.options.raw:
1077 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1082 def status(self, options, args):
1084 retrieve slice status (SliverStatus)
1086 server = self.sliceapi()
1090 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1093 slice_cred = self.slice_credential_string(slice_hrn)
1094 creds = [slice_cred]
1095 if options.delegate:
1096 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1097 creds.append(delegated_cred)
1099 # options and call_id when supported
1101 api_options['call_id']=unique_call_id()
1102 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1103 value = ReturnValue.get_value(result)
1104 if self.options.raw:
1105 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1109 def start(self, options, args):
1111 start named slice (Start)
1113 server = self.sliceapi()
1117 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1120 slice_cred = self.slice_credential_string(args[0])
1121 creds = [slice_cred]
1122 if options.delegate:
1123 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1124 creds.append(delegated_cred)
1125 # xxx Thierry - does this not need an api_options as well ?
1126 result = server.Start(slice_urn, creds)
1127 value = ReturnValue.get_value(result)
1128 if self.options.raw:
1129 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1134 def stop(self, options, args):
1136 stop named slice (Stop)
1138 server = self.sliceapi()
1141 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1143 slice_cred = self.slice_credential_string(args[0])
1144 creds = [slice_cred]
1145 if options.delegate:
1146 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1147 creds.append(delegated_cred)
1148 result = server.Stop(slice_urn, creds)
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)
1157 def reset(self, options, args):
1159 reset named slice (reset_slice)
1161 server = self.sliceapi()
1164 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1166 slice_cred = self.slice_credential_string(args[0])
1167 creds = [slice_cred]
1168 if options.delegate:
1169 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1170 creds.append(delegated_cred)
1171 result = server.reset_slice(creds, slice_urn)
1172 value = ReturnValue.get_value(result)
1173 if self.options.raw:
1174 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1179 def renew(self, options, args):
1181 renew slice (RenewSliver)
1183 server = self.sliceapi()
1186 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1188 slice_cred = self.slice_credential_string(args[0])
1189 creds = [slice_cred]
1190 if options.delegate:
1191 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1192 creds.append(delegated_cred)
1195 # options and call_id when supported
1197 api_options['call_id']=unique_call_id()
1198 result = server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1199 value = ReturnValue.get_value(result)
1200 if self.options.raw:
1201 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1207 def shutdown(self, options, args):
1209 shutdown named slice (Shutdown)
1211 server = self.sliceapi()
1214 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1216 slice_cred = self.slice_credential_string(slice_hrn)
1217 creds = [slice_cred]
1218 if options.delegate:
1219 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1220 creds.append(delegated_cred)
1221 result = server.Shutdown(slice_urn, creds)
1222 value = ReturnValue.get_value(result)
1223 if self.options.raw:
1224 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1230 def get_ticket(self, options, args):
1232 get a ticket for the specified slice
1234 server = self.sliceapi()
1236 slice_hrn, rspec_path = args[0], args[1]
1237 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1239 slice_cred = self.slice_credential_string(slice_hrn)
1240 creds = [slice_cred]
1241 if options.delegate:
1242 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1243 creds.append(delegated_cred)
1245 rspec_file = self.get_rspec_file(rspec_path)
1246 rspec = open(rspec_file).read()
1247 # options and call_id when supported
1249 api_options['call_id']=unique_call_id()
1250 # get ticket at the server
1251 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1253 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1254 self.logger.info("writing ticket to %s"%file)
1255 ticket = SfaTicket(string=ticket_string)
1256 ticket.save_to_file(filename=file, save_parents=True)
1258 def redeem_ticket(self, options, args):
1260 Connects to nodes in a slice and redeems a ticket
1261 (slice hrn is retrieved from the ticket)
1263 ticket_file = args[0]
1265 # get slice hrn from the ticket
1266 # use this to get the right slice credential
1267 ticket = SfaTicket(filename=ticket_file)
1269 ticket_string = ticket.save_to_string(save_parents=True)
1271 slice_hrn = ticket.gidObject.get_hrn()
1272 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1273 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1274 slice_cred = self.slice_credential_string(slice_hrn)
1276 # get a list of node hostnames from the RSpec
1277 tree = etree.parse(StringIO(ticket.rspec))
1278 root = tree.getroot()
1279 hostnames = root.xpath("./network/site/node/hostname/text()")
1281 # create an xmlrpc connection to the component manager at each of these
1282 # components and gall redeem_ticket
1284 for hostname in hostnames:
1286 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1287 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1288 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1289 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1290 timeout=self.options.timeout, verbose=self.options.debug)
1291 server.RedeemTicket(ticket_string, slice_cred)
1292 self.logger.info("Success")
1293 except socket.gaierror:
1294 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1295 except Exception, e:
1296 self.logger.log_exc(e.message)
1299 def create_gid(self, options, args):
1301 Create a GID (CreateGid)
1306 target_hrn = args[0]
1307 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1309 filename = options.file
1311 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1312 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1313 GID(string=gid).save_to_file(filename)
1316 def delegate(self, options, args):
1318 (locally) create delegate credential for use by given hrn
1320 delegee_hrn = args[0]
1321 if options.delegate_user:
1322 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1323 elif options.delegate_slice:
1324 slice_cred = self.slice_credential_string(options.delegate_slice)
1325 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1327 self.logger.warning("Must specify either --user or --slice <hrn>")
1329 delegated_cred = Credential(string=cred)
1330 object_hrn = delegated_cred.get_gid_object().get_hrn()
1331 if options.delegate_user:
1332 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1333 + get_leaf(object_hrn) + ".cred")
1334 elif options.delegate_slice:
1335 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1336 + get_leaf(object_hrn) + ".cred")
1338 delegated_cred.save_to_file(dest_fn, save_parents=True)
1340 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1342 def get_trusted_certs(self, options, args):
1344 return uhe trusted certs at this interface (get_trusted_certs)
1346 trusted_certs = self.registry().get_trusted_certs()
1347 for trusted_cert in trusted_certs:
1348 gid = GID(string=trusted_cert)
1350 cert = Certificate(string=trusted_cert)
1351 self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
1354 def config (self, options, args):
1355 "Display contents of current config"