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):
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
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(sort=True)
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 # we should have a type by now
843 if 'type' not in record_dict :
846 # this is still planetlab dependent.. as plc will whine without that
847 # also, it's only for adding
848 if record_dict['type'] == 'user':
849 if not 'first_name' in record_dict:
850 record_dict['first_name'] = record_dict['hrn']
851 if 'last_name' not in record_dict:
852 record_dict['last_name'] = record_dict['hrn']
853 return self.registry().Register(record_dict, auth_cred)
855 def update(self, options, args):
856 "update record into registry from xml file (Update)"
859 record_filepath = args[0]
860 rec_file = self.get_record_file(record_filepath)
861 record_dict.update(load_record_from_file(rec_file).todict())
863 record_dict.update(load_record_from_opts(options).todict())
864 # at the very least we need 'type' here
865 if 'type' not in record_dict:
869 # don't translate into an object, as this would possibly distort
870 # user-provided data; e.g. add an 'email' field to Users
871 if record_dict['type'] == "user":
872 if record_dict['hrn'] == self.user:
873 cred = self.my_credential_string
875 cred = self.my_authority_credential_string()
876 elif record_dict['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_dict['type'] in ["authority"]:
887 cred = self.my_authority_credential_string()
888 elif record_dict['type'] == 'node':
889 cred = self.my_authority_credential_string()
891 raise "unknown record type" + record_dict['type']
892 return self.registry().Update(record_dict, cred)
894 def remove(self, options, args):
895 "remove registry record by name (Remove)"
896 auth_cred = self.my_authority_credential_string()
904 return self.registry().Remove(hrn, auth_cred, type)
906 # ==================================================================
907 # Slice-related commands
908 # ==================================================================
910 def slices(self, options, args):
911 "list instantiated slices (ListSlices) - returns urn's"
912 server = self.sliceapi()
914 creds = [self.my_credential_string]
916 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
917 creds.append(delegated_cred)
918 # options and call_id when supported
920 api_options['call_id']=unique_call_id()
921 result = server.ListSlices(creds, *self.ois(server,api_options))
922 value = ReturnValue.get_value(result)
924 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
929 # show rspec for named slice
930 def resources(self, options, args):
932 with no arg, discover available resources, (ListResources)
933 or with an slice hrn, shows currently provisioned resources
935 server = self.sliceapi()
940 creds.append(self.slice_credential_string(args[0]))
942 creds.append(self.my_credential_string)
944 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
946 # no need to check if server accepts the options argument since the options has
947 # been a required argument since v1 API
949 # always send call_id to v2 servers
950 api_options ['call_id'] = unique_call_id()
951 # ask for cached value if available
952 api_options ['cached'] = True
955 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
957 api_options['info'] = options.info
959 if options.current == True:
960 api_options['cached'] = False
962 api_options['cached'] = True
963 if options.rspec_version:
964 version_manager = VersionManager()
965 server_version = self.get_cached_server_version(server)
966 if 'sfa' in server_version:
967 # just request the version the client wants
968 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
970 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
972 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
973 result = server.ListResources (creds, api_options)
974 value = ReturnValue.get_value(result)
976 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
977 if options.file is not None:
978 save_rspec_to_file(value, options.file)
979 if (self.options.raw is None) and (options.file is None):
980 display_rspec(value, options.format)
984 def create(self, options, args):
986 create or update named slice with given rspec
988 server = self.sliceapi()
990 # xxx do we need to check usage (len(args)) ?
993 slice_urn = hrn_to_urn(slice_hrn, 'slice')
996 creds = [self.slice_credential_string(slice_hrn)]
997 delegated_cred = None
998 server_version = self.get_cached_server_version(server)
999 if server_version.get('interface') == 'slicemgr':
1000 # delegate our cred to the slice manager
1001 # do not delegate cred to slicemgr...not working at the moment
1003 #if server_version.get('hrn'):
1004 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1005 #elif server_version.get('urn'):
1006 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1009 rspec_file = self.get_rspec_file(args[1])
1010 rspec = open(rspec_file).read()
1013 # need to pass along user keys to the aggregate.
1015 # { urn: urn:publicid:IDN+emulab.net+user+alice
1016 # keys: [<ssh key A>, <ssh key B>]
1019 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1020 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1021 slice_record = slice_records[0]
1022 user_hrns = slice_record['researcher']
1023 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1024 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1026 if 'sfa' not in server_version:
1027 users = pg_users_arg(user_records)
1028 rspec = RSpec(rspec)
1029 rspec.filter({'component_manager_id': server_version['urn']})
1030 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1032 users = sfa_users_arg(user_records, slice_record)
1034 # do not append users, keys, or slice tags. Anything
1035 # not contained in this request will be removed from the slice
1037 # CreateSliver has supported the options argument for a while now so it should
1038 # be safe to assume this server support it
1040 api_options ['append'] = False
1041 api_options ['call_id'] = unique_call_id()
1042 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1043 value = ReturnValue.get_value(result)
1044 if self.options.raw:
1045 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1046 if options.file is not None:
1047 save_rspec_to_file (value, options.file)
1048 if (self.options.raw is None) and (options.file is None):
1053 def delete(self, options, args):
1055 delete named slice (DeleteSliver)
1057 server = self.sliceapi()
1061 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1064 slice_cred = self.slice_credential_string(slice_hrn)
1065 creds = [slice_cred]
1066 if options.delegate:
1067 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1068 creds.append(delegated_cred)
1070 # options and call_id when supported
1072 api_options ['call_id'] = unique_call_id()
1073 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1074 value = ReturnValue.get_value(result)
1075 if self.options.raw:
1076 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1081 def status(self, options, args):
1083 retrieve slice status (SliverStatus)
1085 server = self.sliceapi()
1089 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1092 slice_cred = self.slice_credential_string(slice_hrn)
1093 creds = [slice_cred]
1094 if options.delegate:
1095 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1096 creds.append(delegated_cred)
1098 # options and call_id when supported
1100 api_options['call_id']=unique_call_id()
1101 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1102 value = ReturnValue.get_value(result)
1103 if self.options.raw:
1104 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1108 def start(self, options, args):
1110 start named slice (Start)
1112 server = self.sliceapi()
1116 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1119 slice_cred = self.slice_credential_string(args[0])
1120 creds = [slice_cred]
1121 if options.delegate:
1122 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1123 creds.append(delegated_cred)
1124 # xxx Thierry - does this not need an api_options as well ?
1125 result = server.Start(slice_urn, creds)
1126 value = ReturnValue.get_value(result)
1127 if self.options.raw:
1128 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1133 def stop(self, options, args):
1135 stop named slice (Stop)
1137 server = self.sliceapi()
1140 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1142 slice_cred = self.slice_credential_string(args[0])
1143 creds = [slice_cred]
1144 if options.delegate:
1145 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1146 creds.append(delegated_cred)
1147 result = server.Stop(slice_urn, creds)
1148 value = ReturnValue.get_value(result)
1149 if self.options.raw:
1150 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1156 def reset(self, options, args):
1158 reset named slice (reset_slice)
1160 server = self.sliceapi()
1163 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1165 slice_cred = self.slice_credential_string(args[0])
1166 creds = [slice_cred]
1167 if options.delegate:
1168 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1169 creds.append(delegated_cred)
1170 result = server.reset_slice(creds, slice_urn)
1171 value = ReturnValue.get_value(result)
1172 if self.options.raw:
1173 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1178 def renew(self, options, args):
1180 renew slice (RenewSliver)
1182 server = self.sliceapi()
1185 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)
1194 # options and call_id when supported
1196 api_options['call_id']=unique_call_id()
1197 result = server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1198 value = ReturnValue.get_value(result)
1199 if self.options.raw:
1200 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1206 def shutdown(self, options, args):
1208 shutdown named slice (Shutdown)
1210 server = self.sliceapi()
1213 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1215 slice_cred = self.slice_credential_string(slice_hrn)
1216 creds = [slice_cred]
1217 if options.delegate:
1218 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1219 creds.append(delegated_cred)
1220 result = server.Shutdown(slice_urn, creds)
1221 value = ReturnValue.get_value(result)
1222 if self.options.raw:
1223 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1229 def get_ticket(self, options, args):
1231 get a ticket for the specified slice
1233 server = self.sliceapi()
1235 slice_hrn, rspec_path = args[0], args[1]
1236 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1238 slice_cred = self.slice_credential_string(slice_hrn)
1239 creds = [slice_cred]
1240 if options.delegate:
1241 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1242 creds.append(delegated_cred)
1244 rspec_file = self.get_rspec_file(rspec_path)
1245 rspec = open(rspec_file).read()
1246 # options and call_id when supported
1248 api_options['call_id']=unique_call_id()
1249 # get ticket at the server
1250 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1252 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1253 self.logger.info("writing ticket to %s"%file)
1254 ticket = SfaTicket(string=ticket_string)
1255 ticket.save_to_file(filename=file, save_parents=True)
1257 def redeem_ticket(self, options, args):
1259 Connects to nodes in a slice and redeems a ticket
1260 (slice hrn is retrieved from the ticket)
1262 ticket_file = args[0]
1264 # get slice hrn from the ticket
1265 # use this to get the right slice credential
1266 ticket = SfaTicket(filename=ticket_file)
1268 ticket_string = ticket.save_to_string(save_parents=True)
1270 slice_hrn = ticket.gidObject.get_hrn()
1271 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1272 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1273 slice_cred = self.slice_credential_string(slice_hrn)
1275 # get a list of node hostnames from the RSpec
1276 tree = etree.parse(StringIO(ticket.rspec))
1277 root = tree.getroot()
1278 hostnames = root.xpath("./network/site/node/hostname/text()")
1280 # create an xmlrpc connection to the component manager at each of these
1281 # components and gall redeem_ticket
1283 for hostname in hostnames:
1285 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1286 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1287 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1288 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1289 timeout=self.options.timeout, verbose=self.options.debug)
1290 server.RedeemTicket(ticket_string, slice_cred)
1291 self.logger.info("Success")
1292 except socket.gaierror:
1293 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1294 except Exception, e:
1295 self.logger.log_exc(e.message)
1298 def create_gid(self, options, args):
1300 Create a GID (CreateGid)
1305 target_hrn = args[0]
1306 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1308 filename = options.file
1310 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1311 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1312 GID(string=gid).save_to_file(filename)
1315 def delegate(self, options, args):
1317 (locally) create delegate credential for use by given hrn
1319 delegee_hrn = args[0]
1320 if options.delegate_user:
1321 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1322 elif options.delegate_slice:
1323 slice_cred = self.slice_credential_string(options.delegate_slice)
1324 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1326 self.logger.warning("Must specify either --user or --slice <hrn>")
1328 delegated_cred = Credential(string=cred)
1329 object_hrn = delegated_cred.get_gid_object().get_hrn()
1330 if options.delegate_user:
1331 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1332 + get_leaf(object_hrn) + ".cred")
1333 elif options.delegate_slice:
1334 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1335 + get_leaf(object_hrn) + ".cred")
1337 delegated_cred.save_to_file(dest_fn, save_parents=True)
1339 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1341 def get_trusted_certs(self, options, args):
1343 return uhe trusted certs at this interface (get_trusted_certs)
1345 trusted_certs = self.registry().get_trusted_certs()
1346 for trusted_cert in trusted_certs:
1347 gid = GID(string=trusted_cert)
1349 cert = Certificate(string=trusted_cert)
1350 self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
1353 def config (self, options, args):
1354 "Display contents of current config"