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
46 # utility methods here
47 def optparse_listvalue_callback(option, option_string, value, parser):
48 setattr(parser.values, option.dest, value.split(','))
50 # a code fragment that could be helpful for argparse which unfortunately is
51 # available with 2.7 only, so this feels like too strong a requirement for the client side
52 #class ExtraArgAction (argparse.Action):
53 # def __call__ (self, parser, namespace, values, option_string=None):
54 # would need a try/except of course
55 # (k,v)=values.split('=')
56 # d=getattr(namespace,self.dest)
59 #parser.add_argument ("-X","--extra",dest='extras', default={}, action=ExtraArgAction,
60 # help="set extra flags, testbed dependent, e.g. --extra enabled=true")
62 def optparse_dictvalue_callback (option, option_string, value, parser):
64 (k,v)=value.split('=',1)
65 d=getattr(parser.values, option.dest)
72 def display_rspec(rspec, format='rspec'):
74 tree = etree.parse(StringIO(rspec))
76 result = root.xpath("./network/site/node/hostname/text()")
77 elif format in ['ip']:
78 # The IP address is not yet part of the new RSpec
79 # so this doesn't do anything yet.
80 tree = etree.parse(StringIO(rspec))
82 result = root.xpath("./network/site/node/ipv4/text()")
89 def display_list(results):
90 for result in results:
93 def display_records(recordList, dump=False):
94 ''' Print all fields in the record'''
95 for record in recordList:
96 display_record(record, dump)
98 def display_record(record, dump=False):
100 record.dump(sort=True)
102 info = record.getdict()
103 print "%s (%s)" % (info['hrn'], info['type'])
107 def filter_records(type, records):
108 filtered_records = []
109 for record in records:
110 if (record['type'] == type) or (type == "all"):
111 filtered_records.append(record)
112 return filtered_records
116 def save_raw_to_file(var, filename, format="text", banner=None):
118 # if filename is "-", send it to stdout
121 f = open(filename, "w")
126 elif format == "pickled":
127 f.write(pickle.dumps(var))
128 elif format == "json":
129 if hasattr(json, "dumps"):
130 f.write(json.dumps(var)) # python 2.6
132 f.write(json.write(var)) # python 2.5
134 # this should never happen
135 print "unknown output format", format
137 f.write('\n'+banner+"\n")
139 def save_rspec_to_file(rspec, filename):
140 if not filename.endswith(".rspec"):
141 filename = filename + ".rspec"
142 f = open(filename, 'w')
147 def save_records_to_file(filename, record_dicts, format="xml"):
150 for record_dict in record_dicts:
152 save_record_to_file(filename + "." + str(index), record_dict)
154 save_record_to_file(filename, record_dict)
156 elif format == "xmllist":
157 f = open(filename, "w")
158 f.write("<recordlist>\n")
159 for record_dict in record_dicts:
160 record_obj=Record(dict=record_dict)
161 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
162 f.write("</recordlist>\n")
164 elif format == "hrnlist":
165 f = open(filename, "w")
166 for record_dict in record_dicts:
167 record_obj=Record(dict=record_dict)
168 f.write(record_obj.hrn + "\n")
171 # this should never happen
172 print "unknown output format", format
174 def save_record_to_file(filename, record_dict):
175 record = Record(dict=record_dict)
176 xml = record.save_as_xml()
177 f=codecs.open(filename, encoding='utf-8',mode="w")
182 # minimally check a key argument
183 def check_ssh_key (key):
184 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
185 return re.match(good_ssh_key, key, re.IGNORECASE)
188 def load_record_from_opts(options):
190 if hasattr(options, 'xrn') and options.xrn:
191 if hasattr(options, 'type') and options.type:
192 xrn = Xrn(options.xrn, options.type)
194 xrn = Xrn(options.xrn)
195 record_dict['urn'] = xrn.get_urn()
196 record_dict['hrn'] = xrn.get_hrn()
197 record_dict['type'] = xrn.get_type()
198 if hasattr(options, 'key') and options.key:
200 pubkey = open(options.key, 'r').read()
203 if not check_ssh_key (pubkey):
204 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
205 record_dict['keys'] = [pubkey]
206 if hasattr(options, 'slices') and options.slices:
207 record_dict['slices'] = options.slices
208 if hasattr(options, 'researchers') and options.researchers:
209 record_dict['researcher'] = options.researchers
210 if hasattr(options, 'email') and options.email:
211 record_dict['email'] = options.email
212 if hasattr(options, 'pis') and options.pis:
213 record_dict['pi'] = options.pis
215 # handle extra settings
216 record_dict.update(options.extras)
218 return Record(dict=record_dict)
220 def load_record_from_file(filename):
221 f=codecs.open(filename, encoding="utf-8", mode="r")
222 xml_string = f.read()
224 return Record(xml=xml_string)
228 def unique_call_id(): return uuid.uuid4().urn
232 # dirty hack to make this class usable from the outside
233 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
236 def default_sfi_dir ():
237 if os.path.isfile("./sfi_config"):
240 return os.path.expanduser("~/.sfi/")
242 # dummy to meet Sfi's expectations for its 'options' field
243 # i.e. s/t we can do setattr on
247 def __init__ (self,options=None):
248 if options is None: options=Sfi.DummyOptions()
249 for opt in Sfi.required_options:
250 if not hasattr(options,opt): setattr(options,opt,None)
251 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
252 self.options = options
254 self.authority = None
255 self.logger = sfi_logger
256 self.logger.enable_console()
257 self.available_names = [ tuple[0] for tuple in Sfi.available ]
258 self.available_dict = dict (Sfi.available)
260 # tuples command-name expected-args in the order in which they should appear in the help
263 ("list", "authority"),
266 ("update", "record"),
269 ("resources", "[slice_hrn]"),
270 ("create", "slice_hrn rspec"),
271 ("delete", "slice_hrn"),
272 ("status", "slice_hrn"),
273 ("start", "slice_hrn"),
274 ("stop", "slice_hrn"),
275 ("reset", "slice_hrn"),
276 ("renew", "slice_hrn time"),
277 ("shutdown", "slice_hrn"),
278 ("get_ticket", "slice_hrn rspec"),
279 ("redeem_ticket", "ticket"),
280 ("delegate", "name"),
281 ("create_gid", "[name]"),
282 ("get_trusted_certs", "cred"),
286 def print_command_help (self, options):
287 verbose=getattr(options,'verbose')
288 format3="%18s %-15s %s"
291 print format3%("command","cmd_args","description")
295 self.create_parser().print_help()
296 for command in self.available_names:
297 args=self.available_dict[command]
298 method=getattr(self,command,None)
300 if method: doc=getattr(method,'__doc__',"")
301 if not doc: doc="*** no doc found ***"
302 doc=doc.strip(" \t\n")
303 doc=doc.replace("\n","\n"+35*' ')
306 print format3%(command,args,doc)
308 self.create_command_parser(command).print_help()
310 def create_command_parser(self, command):
311 if command not in self.available_dict:
312 msg="Invalid command\n"
314 msg += ','.join(self.available_names)
315 self.logger.critical(msg)
318 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
319 % (command, self.available_dict[command]))
321 if command in ("add", "update"):
322 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
323 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
324 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
325 # use --extra instead
326 # parser.add_option('-u', '--url', dest='url', metavar='<url>', default=None, help="URL, useful for slices")
327 # parser.add_option('-d', '--description', dest='description', metavar='<description>',
328 # help='Description, useful for slices', default=None)
329 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
331 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='slice xrns',
332 default='', type="str", action='callback', callback=optparse_listvalue_callback)
333 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
334 help='slice researchers', default='', type="str", action='callback',
335 callback=optparse_listvalue_callback)
336 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Principal Investigators/Project Managers',
337 default='', type="str", action='callback', callback=optparse_listvalue_callback)
338 # use --extra instead
339 # parser.add_option('-f', '--firstname', dest='firstname', metavar='<firstname>', help='user first name')
340 # parser.add_option('-l', '--lastname', dest='lastname', metavar='<lastname>', help='user last name')
341 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
342 action="callback", callback=optparse_dictvalue_callback, nargs=1,
343 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
345 # user specifies remote aggregate/sm/component
346 if command in ("resources", "slices", "create", "delete", "start", "stop",
347 "restart", "shutdown", "get_ticket", "renew", "status"):
348 parser.add_option("-d", "--delegate", dest="delegate", default=None,
350 help="Include a credential delegated to the user's root"+\
351 "authority in set of credentials for this call")
353 # registy filter option
354 if command in ("list", "show", "remove"):
355 parser.add_option("-t", "--type", dest="type", type="choice",
356 help="type filter ([all]|user|slice|authority|node|aggregate)",
357 choices=("all", "user", "slice", "authority", "node", "aggregate"),
359 if command in ("resources"):
361 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
362 help="schema type and version of resulting RSpec")
363 # disable/enable cached rspecs
364 parser.add_option("-c", "--current", dest="current", default=False,
366 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
368 parser.add_option("-f", "--format", dest="format", type="choice",
369 help="display format ([xml]|dns|ip)", default="xml",
370 choices=("xml", "dns", "ip"))
371 #panos: a new option to define the type of information about resources a user is interested in
372 parser.add_option("-i", "--info", dest="info",
373 help="optional component information", default=None)
374 # a new option to retreive or not reservation-oriented RSpecs (leases)
375 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
376 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
377 choices=("all", "resources", "leases"), default="resources")
380 # 'create' does return the new rspec, makes sense to save that too
381 if command in ("resources", "show", "list", "create_gid", 'create'):
382 parser.add_option("-o", "--output", dest="file",
383 help="output XML to file", metavar="FILE", default=None)
385 if command in ("show", "list"):
386 parser.add_option("-f", "--format", dest="format", type="choice",
387 help="display format ([text]|xml)", default="text",
388 choices=("text", "xml"))
390 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
391 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
392 choices=("xml", "xmllist", "hrnlist"))
393 if command == 'list':
394 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
395 help="list all child records", default=False)
396 if command in ("delegate"):
397 parser.add_option("-u", "--user",
398 action="store_true", dest="delegate_user", default=False,
399 help="delegate user credential")
400 parser.add_option("-s", "--slice", dest="delegate_slice",
401 help="delegate slice credential", metavar="HRN", default=None)
403 if command in ("version"):
404 parser.add_option("-R","--registry-version",
405 action="store_true", dest="version_registry", default=False,
406 help="probe registry version instead of sliceapi")
407 parser.add_option("-l","--local",
408 action="store_true", dest="version_local", default=False,
409 help="display version of the local client")
414 def create_parser(self):
416 # Generate command line parser
417 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
418 description="Commands: %s"%(" ".join(self.available_names)))
419 parser.add_option("-r", "--registry", dest="registry",
420 help="root registry", metavar="URL", default=None)
421 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
422 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
423 parser.add_option("-R", "--raw", dest="raw", default=None,
424 help="Save raw, unparsed server response to a file")
425 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
426 help="raw file format ([text]|pickled|json)", default="text",
427 choices=("text","pickled","json"))
428 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
429 help="text string to write before and after raw output")
430 parser.add_option("-d", "--dir", dest="sfi_dir",
431 help="config & working directory - default is %default",
432 metavar="PATH", default=Sfi.default_sfi_dir())
433 parser.add_option("-u", "--user", dest="user",
434 help="user name", metavar="HRN", default=None)
435 parser.add_option("-a", "--auth", dest="auth",
436 help="authority name", metavar="HRN", default=None)
437 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
438 help="verbose mode - cumulative")
439 parser.add_option("-D", "--debug",
440 action="store_true", dest="debug", default=False,
441 help="Debug (xml-rpc) protocol messages")
442 # would it make sense to use ~/.ssh/id_rsa as a default here ?
443 parser.add_option("-k", "--private-key",
444 action="store", dest="user_private_key", default=None,
445 help="point to the private key file to use if not yet installed in sfi_dir")
446 parser.add_option("-t", "--timeout", dest="timeout", default=None,
447 help="Amout of time to wait before timing out the request")
448 parser.add_option("-?", "--commands",
449 action="store_true", dest="command_help", default=False,
450 help="one page summary on commands & exit")
451 parser.disable_interspersed_args()
456 def print_help (self):
457 self.sfi_parser.print_help()
458 self.command_parser.print_help()
461 # Main: parse arguments and dispatch to command
463 def dispatch(self, command, command_options, command_args):
464 return getattr(self, command)(command_options, command_args)
467 self.sfi_parser = self.create_parser()
468 (options, args) = self.sfi_parser.parse_args()
469 if options.command_help:
470 self.print_command_help(options)
472 self.options = options
474 self.logger.setLevelFromOptVerbose(self.options.verbose)
477 self.logger.critical("No command given. Use -h for help.")
478 self.print_command_help(options)
482 self.command_parser = self.create_command_parser(command)
483 (command_options, command_args) = self.command_parser.parse_args(args[1:])
484 self.command_options = command_options
488 self.logger.info("Command=%s" % command)
491 self.dispatch(command, command_options, command_args)
493 self.logger.critical ("Unknown command %s"%command)
500 def read_config(self):
501 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
503 config = Config (config_file)
505 self.logger.critical("Failed to read configuration file %s"%config_file)
506 self.logger.info("Make sure to remove the export clauses and to add quotes")
507 if self.options.verbose==0:
508 self.logger.info("Re-run with -v for more details")
510 self.logger.log_exc("Could not read config file %s"%config_file)
515 if (self.options.sm is not None):
516 self.sm_url = self.options.sm
517 elif hasattr(config, "SFI_SM"):
518 self.sm_url = config.SFI_SM
520 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
524 if (self.options.registry is not None):
525 self.reg_url = self.options.registry
526 elif hasattr(config, "SFI_REGISTRY"):
527 self.reg_url = config.SFI_REGISTRY
529 self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
533 if (self.options.user is not None):
534 self.user = self.options.user
535 elif hasattr(config, "SFI_USER"):
536 self.user = config.SFI_USER
538 self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
542 if (self.options.auth is not None):
543 self.authority = self.options.auth
544 elif hasattr(config, "SFI_AUTH"):
545 self.authority = config.SFI_AUTH
547 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
550 self.config_file=config_file
554 def show_config (self):
555 print "From configuration file %s"%self.config_file
558 ('SFI_AUTH','authority'),
560 ('SFI_REGISTRY','reg_url'),
562 for (external_name, internal_name) in flags:
563 print "%s='%s'"%(external_name,getattr(self,internal_name))
566 # Get various credential and spec files
568 # Establishes limiting conventions
569 # - conflates MAs and SAs
570 # - assumes last token in slice name is unique
572 # Bootstraps credentials
573 # - bootstrap user credential from self-signed certificate
574 # - bootstrap authority credential from user credential
575 # - bootstrap slice credential from user credential
578 # init self-signed cert, user credentials and gid
579 def bootstrap (self):
580 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
581 # if -k is provided, use this to initialize private key
582 if self.options.user_private_key:
583 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
585 # trigger legacy compat code if needed
586 # the name has changed from just <leaf>.pkey to <hrn>.pkey
587 if not os.path.isfile(client_bootstrap.private_key_filename()):
588 self.logger.info ("private key not found, trying legacy name")
590 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
591 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
592 client_bootstrap.init_private_key_if_missing (legacy_private_key)
593 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
595 self.logger.log_exc("Can't find private key ")
599 client_bootstrap.bootstrap_my_gid()
600 # extract what's needed
601 self.private_key = client_bootstrap.private_key()
602 self.my_credential_string = client_bootstrap.my_credential_string ()
603 self.my_gid = client_bootstrap.my_gid ()
604 self.client_bootstrap = client_bootstrap
607 def my_authority_credential_string(self):
608 if not self.authority:
609 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
611 return self.client_bootstrap.authority_credential_string (self.authority)
613 def slice_credential_string(self, name):
614 return self.client_bootstrap.slice_credential_string (name)
616 # xxx should be supported by sfaclientbootstrap as well
617 def delegate_cred(self, object_cred, hrn, type='authority'):
618 # the gid and hrn of the object we are delegating
619 if isinstance(object_cred, str):
620 object_cred = Credential(string=object_cred)
621 object_gid = object_cred.get_gid_object()
622 object_hrn = object_gid.get_hrn()
624 if not object_cred.get_privileges().get_all_delegate():
625 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
628 # the delegating user's gid
629 caller_gidfile = self.my_gid()
631 # the gid of the user who will be delegated to
632 delegee_gid = self.client_bootstrap.gid(hrn,type)
633 delegee_hrn = delegee_gid.get_hrn()
634 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
635 return dcred.save_to_string(save_parents=True)
638 # Management of the servers
643 if not hasattr (self, 'registry_proxy'):
644 self.logger.info("Contacting Registry at: %s"%self.reg_url)
645 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
646 timeout=self.options.timeout, verbose=self.options.debug)
647 return self.registry_proxy
651 if not hasattr (self, 'sliceapi_proxy'):
652 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
653 if hasattr(self.command_options,'component') and self.command_options.component:
654 # resolve the hrn at the registry
655 node_hrn = self.command_options.component
656 records = self.registry().Resolve(node_hrn, self.my_credential_string)
657 records = filter_records('node', records)
659 self.logger.warning("No such component:%r"% opts.component)
661 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
662 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
664 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
665 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
666 self.sm_url = 'http://' + self.sm_url
667 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
668 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
669 timeout=self.options.timeout, verbose=self.options.debug)
670 return self.sliceapi_proxy
672 def get_cached_server_version(self, server):
673 # check local cache first
676 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
677 cache_key = server.url + "-version"
679 cache = Cache(cache_file)
682 self.logger.info("Local cache not found at: %s" % cache_file)
685 version = cache.get(cache_key)
688 result = server.GetVersion()
689 version= ReturnValue.get_value(result)
690 # cache version for 20 minutes
691 cache.add(cache_key, version, ttl= 60*20)
692 self.logger.info("Updating cache file %s" % cache_file)
693 cache.save_to_file(cache_file)
697 ### resurrect this temporarily so we can support V1 aggregates for a while
698 def server_supports_options_arg(self, server):
700 Returns true if server support the optional call_id arg, false otherwise.
702 server_version = self.get_cached_server_version(server)
704 # xxx need to rewrite this
705 if int(server_version.get('geni_api')) >= 2:
709 def server_supports_call_id_arg(self, server):
710 server_version = self.get_cached_server_version(server)
712 if 'sfa' in server_version and 'code_tag' in server_version:
713 code_tag = server_version['code_tag']
714 code_tag_parts = code_tag.split("-")
715 version_parts = code_tag_parts[0].split(".")
716 major, minor = version_parts[0], version_parts[1]
717 rev = code_tag_parts[1]
718 if int(major) == 1 and minor == 0 and build >= 22:
722 ### ois = options if supported
723 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
724 def ois (self, server, option_dict):
725 if self.server_supports_options_arg (server):
727 elif self.server_supports_call_id_arg (server):
728 return [ unique_call_id () ]
732 ### cis = call_id if supported - like ois
733 def cis (self, server):
734 if self.server_supports_call_id_arg (server):
735 return [ unique_call_id ]
739 ######################################## miscell utilities
740 def get_rspec_file(self, rspec):
741 if (os.path.isabs(rspec)):
744 file = os.path.join(self.options.sfi_dir, rspec)
745 if (os.path.isfile(file)):
748 self.logger.critical("No such rspec file %s"%rspec)
751 def get_record_file(self, record):
752 if (os.path.isabs(record)):
755 file = os.path.join(self.options.sfi_dir, record)
756 if (os.path.isfile(file)):
759 self.logger.critical("No such registry record file %s"%record)
763 #==========================================================================
764 # Following functions implement the commands
766 # Registry-related commands
767 #==========================================================================
769 def version(self, options, args):
771 display an SFA server version (GetVersion)
772 or version information about sfi itself
774 if options.version_local:
775 version=version_core()
777 if options.version_registry:
778 server=self.registry()
780 server = self.sliceapi()
781 result = server.GetVersion()
782 version = ReturnValue.get_value(result)
784 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
786 pprinter = PrettyPrinter(indent=4)
787 pprinter.pprint(version)
789 def list(self, options, args):
791 list entries in named authority registry (List)
798 if options.recursive:
799 opts['recursive'] = options.recursive
802 list = self.registry().List(hrn, self.my_credential_string, options)
804 raise Exception, "Not enough parameters for the 'list' command"
806 # filter on person, slice, site, node, etc.
807 # THis really should be in the self.filter_records funct def comment...
808 list = filter_records(options.type, list)
810 print "%s (%s)" % (record['hrn'], record['type'])
812 save_records_to_file(options.file, list, options.fileformat)
815 def show(self, options, args):
817 show details about named registry record (Resolve)
823 record_dicts = self.registry().Resolve(hrn, self.my_credential_string)
824 record_dicts = filter_records(options.type, record_dicts)
826 self.logger.error("No record of type %s"% options.type)
827 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
828 for record in records:
829 if (options.format == "text"): record.dump(sort=True)
830 else: print record.save_as_xml()
832 save_records_to_file(options.file, record_dicts, options.fileformat)
835 def add(self, options, args):
836 "add record into registry from xml file (Register)"
837 auth_cred = self.my_authority_credential_string()
840 record_filepath = args[0]
841 rec_file = self.get_record_file(record_filepath)
842 record_dict.update(load_record_from_file(rec_file).todict())
844 record_dict.update(load_record_from_opts(options).todict())
845 # we should have a type by now
846 if 'type' not in record_dict :
849 # this is still planetlab dependent.. as plc will whine without that
850 # also, it's only for adding
851 if record_dict['type'] == 'user':
852 if not 'first_name' in record_dict:
853 record_dict['first_name'] = record_dict['hrn']
854 if 'last_name' not in record_dict:
855 record_dict['last_name'] = record_dict['hrn']
856 return self.registry().Register(record_dict, auth_cred)
858 def update(self, options, args):
859 "update record into registry from xml file (Update)"
862 record_filepath = args[0]
863 rec_file = self.get_record_file(record_filepath)
864 record_dict.update(load_record_from_file(rec_file).todict())
866 record_dict.update(load_record_from_opts(options).todict())
867 # at the very least we need 'type' here
868 if 'type' not in record_dict:
872 # don't translate into an object, as this would possibly distort
873 # user-provided data; e.g. add an 'email' field to Users
874 if record_dict['type'] == "user":
875 if record_dict['hrn'] == self.user:
876 cred = self.my_credential_string
878 cred = self.my_authority_credential_string()
879 elif record_dict['type'] in ["slice"]:
881 cred = self.slice_credential_string(record_dict['hrn'])
882 except ServerException, e:
883 # XXX smbaker -- once we have better error return codes, update this
884 # to do something better than a string compare
885 if "Permission error" in e.args[0]:
886 cred = self.my_authority_credential_string()
889 elif record_dict['type'] in ["authority"]:
890 cred = self.my_authority_credential_string()
891 elif record_dict['type'] == 'node':
892 cred = self.my_authority_credential_string()
894 raise "unknown record type" + record_dict['type']
895 return self.registry().Update(record_dict, cred)
897 def remove(self, options, args):
898 "remove registry record by name (Remove)"
899 auth_cred = self.my_authority_credential_string()
907 return self.registry().Remove(hrn, auth_cred, type)
909 # ==================================================================
910 # Slice-related commands
911 # ==================================================================
913 def slices(self, options, args):
914 "list instantiated slices (ListSlices) - returns urn's"
915 server = self.sliceapi()
917 creds = [self.my_credential_string]
919 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
920 creds.append(delegated_cred)
921 # options and call_id when supported
923 api_options['call_id']=unique_call_id()
924 result = server.ListSlices(creds, *self.ois(server,api_options))
925 value = ReturnValue.get_value(result)
927 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
932 # show rspec for named slice
933 def resources(self, options, args):
935 with no arg, discover available resources, (ListResources)
936 or with an slice hrn, shows currently provisioned resources
938 server = self.sliceapi()
943 creds.append(self.slice_credential_string(args[0]))
945 creds.append(self.my_credential_string)
947 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
949 # no need to check if server accepts the options argument since the options has
950 # been a required argument since v1 API
952 # always send call_id to v2 servers
953 api_options ['call_id'] = unique_call_id()
954 # ask for cached value if available
955 api_options ['cached'] = True
958 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
960 api_options['info'] = options.info
961 if options.list_leases:
962 api_options['list_leases'] = options.list_leases
964 if options.current == True:
965 api_options['cached'] = False
967 api_options['cached'] = True
968 if options.rspec_version:
969 version_manager = VersionManager()
970 server_version = self.get_cached_server_version(server)
971 if 'sfa' in server_version:
972 # just request the version the client wants
973 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
975 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
977 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
978 result = server.ListResources (creds, api_options)
979 value = ReturnValue.get_value(result)
981 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
982 if options.file is not None:
983 save_rspec_to_file(value, options.file)
984 if (self.options.raw is None) and (options.file is None):
985 display_rspec(value, options.format)
989 def create(self, options, args):
991 create or update named slice with given rspec
993 server = self.sliceapi()
995 # xxx do we need to check usage (len(args)) ?
998 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1001 creds = [self.slice_credential_string(slice_hrn)]
1002 delegated_cred = None
1003 server_version = self.get_cached_server_version(server)
1004 if server_version.get('interface') == 'slicemgr':
1005 # delegate our cred to the slice manager
1006 # do not delegate cred to slicemgr...not working at the moment
1008 #if server_version.get('hrn'):
1009 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1010 #elif server_version.get('urn'):
1011 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1014 rspec_file = self.get_rspec_file(args[1])
1015 rspec = open(rspec_file).read()
1018 # need to pass along user keys to the aggregate.
1020 # { urn: urn:publicid:IDN+emulab.net+user+alice
1021 # keys: [<ssh key A>, <ssh key B>]
1024 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1025 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1026 slice_record = slice_records[0]
1027 user_hrns = slice_record['researcher']
1028 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1029 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1031 if 'sfa' not in server_version:
1032 users = pg_users_arg(user_records)
1033 rspec = RSpec(rspec)
1034 rspec.filter({'component_manager_id': server_version['urn']})
1035 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1037 users = sfa_users_arg(user_records, slice_record)
1039 # do not append users, keys, or slice tags. Anything
1040 # not contained in this request will be removed from the slice
1042 # CreateSliver has supported the options argument for a while now so it should
1043 # be safe to assume this server support it
1045 api_options ['append'] = False
1046 api_options ['call_id'] = unique_call_id()
1047 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1048 value = ReturnValue.get_value(result)
1049 if self.options.raw:
1050 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1051 if options.file is not None:
1052 save_rspec_to_file (value, options.file)
1053 if (self.options.raw is None) and (options.file is None):
1058 def delete(self, options, args):
1060 delete named slice (DeleteSliver)
1062 server = self.sliceapi()
1066 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1069 slice_cred = self.slice_credential_string(slice_hrn)
1070 creds = [slice_cred]
1071 if options.delegate:
1072 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1073 creds.append(delegated_cred)
1075 # options and call_id when supported
1077 api_options ['call_id'] = unique_call_id()
1078 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1079 value = ReturnValue.get_value(result)
1080 if self.options.raw:
1081 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1086 def status(self, options, args):
1088 retrieve slice status (SliverStatus)
1090 server = self.sliceapi()
1094 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1097 slice_cred = self.slice_credential_string(slice_hrn)
1098 creds = [slice_cred]
1099 if options.delegate:
1100 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1101 creds.append(delegated_cred)
1103 # options and call_id when supported
1105 api_options['call_id']=unique_call_id()
1106 result = server.SliverStatus(slice_urn, creds, *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)
1113 def start(self, options, args):
1115 start named slice (Start)
1117 server = self.sliceapi()
1121 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1124 slice_cred = self.slice_credential_string(args[0])
1125 creds = [slice_cred]
1126 if options.delegate:
1127 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1128 creds.append(delegated_cred)
1129 # xxx Thierry - does this not need an api_options as well ?
1130 result = server.Start(slice_urn, creds)
1131 value = ReturnValue.get_value(result)
1132 if self.options.raw:
1133 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1138 def stop(self, options, args):
1140 stop named slice (Stop)
1142 server = self.sliceapi()
1145 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1147 slice_cred = self.slice_credential_string(args[0])
1148 creds = [slice_cred]
1149 if options.delegate:
1150 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1151 creds.append(delegated_cred)
1152 result = server.Stop(slice_urn, creds)
1153 value = ReturnValue.get_value(result)
1154 if self.options.raw:
1155 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1161 def reset(self, options, args):
1163 reset named slice (reset_slice)
1165 server = self.sliceapi()
1168 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1170 slice_cred = self.slice_credential_string(args[0])
1171 creds = [slice_cred]
1172 if options.delegate:
1173 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1174 creds.append(delegated_cred)
1175 result = server.reset_slice(creds, slice_urn)
1176 value = ReturnValue.get_value(result)
1177 if self.options.raw:
1178 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1183 def renew(self, options, args):
1185 renew slice (RenewSliver)
1187 server = self.sliceapi()
1190 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1192 slice_cred = self.slice_credential_string(args[0])
1193 creds = [slice_cred]
1194 if options.delegate:
1195 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1196 creds.append(delegated_cred)
1199 # options and call_id when supported
1201 api_options['call_id']=unique_call_id()
1202 result = server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1203 value = ReturnValue.get_value(result)
1204 if self.options.raw:
1205 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1211 def shutdown(self, options, args):
1213 shutdown named slice (Shutdown)
1215 server = self.sliceapi()
1218 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1220 slice_cred = self.slice_credential_string(slice_hrn)
1221 creds = [slice_cred]
1222 if options.delegate:
1223 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1224 creds.append(delegated_cred)
1225 result = server.Shutdown(slice_urn, creds)
1226 value = ReturnValue.get_value(result)
1227 if self.options.raw:
1228 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1234 def get_ticket(self, options, args):
1236 get a ticket for the specified slice
1238 server = self.sliceapi()
1240 slice_hrn, rspec_path = args[0], args[1]
1241 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1243 slice_cred = self.slice_credential_string(slice_hrn)
1244 creds = [slice_cred]
1245 if options.delegate:
1246 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1247 creds.append(delegated_cred)
1249 rspec_file = self.get_rspec_file(rspec_path)
1250 rspec = open(rspec_file).read()
1251 # options and call_id when supported
1253 api_options['call_id']=unique_call_id()
1254 # get ticket at the server
1255 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1257 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1258 self.logger.info("writing ticket to %s"%file)
1259 ticket = SfaTicket(string=ticket_string)
1260 ticket.save_to_file(filename=file, save_parents=True)
1262 def redeem_ticket(self, options, args):
1264 Connects to nodes in a slice and redeems a ticket
1265 (slice hrn is retrieved from the ticket)
1267 ticket_file = args[0]
1269 # get slice hrn from the ticket
1270 # use this to get the right slice credential
1271 ticket = SfaTicket(filename=ticket_file)
1273 ticket_string = ticket.save_to_string(save_parents=True)
1275 slice_hrn = ticket.gidObject.get_hrn()
1276 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1277 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1278 slice_cred = self.slice_credential_string(slice_hrn)
1280 # get a list of node hostnames from the RSpec
1281 tree = etree.parse(StringIO(ticket.rspec))
1282 root = tree.getroot()
1283 hostnames = root.xpath("./network/site/node/hostname/text()")
1285 # create an xmlrpc connection to the component manager at each of these
1286 # components and gall redeem_ticket
1288 for hostname in hostnames:
1290 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1291 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1292 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1293 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1294 timeout=self.options.timeout, verbose=self.options.debug)
1295 server.RedeemTicket(ticket_string, slice_cred)
1296 self.logger.info("Success")
1297 except socket.gaierror:
1298 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1299 except Exception, e:
1300 self.logger.log_exc(e.message)
1303 def create_gid(self, options, args):
1305 Create a GID (CreateGid)
1310 target_hrn = args[0]
1311 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1313 filename = options.file
1315 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1316 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1317 GID(string=gid).save_to_file(filename)
1320 def delegate(self, options, args):
1322 (locally) create delegate credential for use by given hrn
1324 delegee_hrn = args[0]
1325 if options.delegate_user:
1326 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1327 elif options.delegate_slice:
1328 slice_cred = self.slice_credential_string(options.delegate_slice)
1329 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1331 self.logger.warning("Must specify either --user or --slice <hrn>")
1333 delegated_cred = Credential(string=cred)
1334 object_hrn = delegated_cred.get_gid_object().get_hrn()
1335 if options.delegate_user:
1336 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1337 + get_leaf(object_hrn) + ".cred")
1338 elif options.delegate_slice:
1339 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1340 + get_leaf(object_hrn) + ".cred")
1342 delegated_cred.save_to_file(dest_fn, save_parents=True)
1344 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1346 def get_trusted_certs(self, options, args):
1348 return uhe trusted certs at this interface (get_trusted_certs)
1350 trusted_certs = self.registry().get_trusted_certs()
1351 for trusted_cert in trusted_certs:
1352 gid = GID(string=trusted_cert)
1354 cert = Certificate(string=trusted_cert)
1355 self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
1358 def config (self, options, args):
1359 "Display contents of current config"