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 print >>sys.stderr, "\r\n \r\n \r\n WOOOOOO"
1038 users = sfa_users_arg(user_records, slice_record)
1040 # do not append users, keys, or slice tags. Anything
1041 # not contained in this request will be removed from the slice
1043 # CreateSliver has supported the options argument for a while now so it should
1044 # be safe to assume this server support it
1046 api_options ['append'] = False
1047 api_options ['call_id'] = unique_call_id()
1048 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1049 value = ReturnValue.get_value(result)
1050 if self.options.raw:
1051 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1052 if options.file is not None:
1053 save_rspec_to_file (value, options.file)
1054 if (self.options.raw is None) and (options.file is None):
1059 def delete(self, options, args):
1061 delete named slice (DeleteSliver)
1063 server = self.sliceapi()
1067 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1070 slice_cred = self.slice_credential_string(slice_hrn)
1071 creds = [slice_cred]
1072 if options.delegate:
1073 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1074 creds.append(delegated_cred)
1076 # options and call_id when supported
1078 api_options ['call_id'] = unique_call_id()
1079 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1080 value = ReturnValue.get_value(result)
1081 if self.options.raw:
1082 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1087 def status(self, options, args):
1089 retrieve slice status (SliverStatus)
1091 server = self.sliceapi()
1095 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1098 slice_cred = self.slice_credential_string(slice_hrn)
1099 creds = [slice_cred]
1100 if options.delegate:
1101 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1102 creds.append(delegated_cred)
1104 # options and call_id when supported
1106 api_options['call_id']=unique_call_id()
1107 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1108 value = ReturnValue.get_value(result)
1109 if self.options.raw:
1110 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1114 def start(self, options, args):
1116 start named slice (Start)
1118 server = self.sliceapi()
1122 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1125 slice_cred = self.slice_credential_string(args[0])
1126 creds = [slice_cred]
1127 if options.delegate:
1128 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1129 creds.append(delegated_cred)
1130 # xxx Thierry - does this not need an api_options as well ?
1131 result = server.Start(slice_urn, creds)
1132 value = ReturnValue.get_value(result)
1133 if self.options.raw:
1134 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1139 def stop(self, options, args):
1141 stop named slice (Stop)
1143 server = self.sliceapi()
1146 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1148 slice_cred = self.slice_credential_string(args[0])
1149 creds = [slice_cred]
1150 if options.delegate:
1151 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1152 creds.append(delegated_cred)
1153 result = server.Stop(slice_urn, creds)
1154 value = ReturnValue.get_value(result)
1155 if self.options.raw:
1156 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1162 def reset(self, options, args):
1164 reset named slice (reset_slice)
1166 server = self.sliceapi()
1169 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1171 slice_cred = self.slice_credential_string(args[0])
1172 creds = [slice_cred]
1173 if options.delegate:
1174 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1175 creds.append(delegated_cred)
1176 result = server.reset_slice(creds, slice_urn)
1177 value = ReturnValue.get_value(result)
1178 if self.options.raw:
1179 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1184 def renew(self, options, args):
1186 renew slice (RenewSliver)
1188 server = self.sliceapi()
1191 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1193 slice_cred = self.slice_credential_string(args[0])
1194 creds = [slice_cred]
1195 if options.delegate:
1196 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1197 creds.append(delegated_cred)
1200 # options and call_id when supported
1202 api_options['call_id']=unique_call_id()
1203 result = server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1204 value = ReturnValue.get_value(result)
1205 if self.options.raw:
1206 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1212 def shutdown(self, options, args):
1214 shutdown named slice (Shutdown)
1216 server = self.sliceapi()
1219 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1221 slice_cred = self.slice_credential_string(slice_hrn)
1222 creds = [slice_cred]
1223 if options.delegate:
1224 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1225 creds.append(delegated_cred)
1226 result = server.Shutdown(slice_urn, creds)
1227 value = ReturnValue.get_value(result)
1228 if self.options.raw:
1229 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1235 def get_ticket(self, options, args):
1237 get a ticket for the specified slice
1239 server = self.sliceapi()
1241 slice_hrn, rspec_path = args[0], args[1]
1242 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1244 slice_cred = self.slice_credential_string(slice_hrn)
1245 creds = [slice_cred]
1246 if options.delegate:
1247 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1248 creds.append(delegated_cred)
1250 rspec_file = self.get_rspec_file(rspec_path)
1251 rspec = open(rspec_file).read()
1252 # options and call_id when supported
1254 api_options['call_id']=unique_call_id()
1255 # get ticket at the server
1256 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1258 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1259 self.logger.info("writing ticket to %s"%file)
1260 ticket = SfaTicket(string=ticket_string)
1261 ticket.save_to_file(filename=file, save_parents=True)
1263 def redeem_ticket(self, options, args):
1265 Connects to nodes in a slice and redeems a ticket
1266 (slice hrn is retrieved from the ticket)
1268 ticket_file = args[0]
1270 # get slice hrn from the ticket
1271 # use this to get the right slice credential
1272 ticket = SfaTicket(filename=ticket_file)
1274 ticket_string = ticket.save_to_string(save_parents=True)
1276 slice_hrn = ticket.gidObject.get_hrn()
1277 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1278 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1279 slice_cred = self.slice_credential_string(slice_hrn)
1281 # get a list of node hostnames from the RSpec
1282 tree = etree.parse(StringIO(ticket.rspec))
1283 root = tree.getroot()
1284 hostnames = root.xpath("./network/site/node/hostname/text()")
1286 # create an xmlrpc connection to the component manager at each of these
1287 # components and gall redeem_ticket
1289 for hostname in hostnames:
1291 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1292 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1293 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1294 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1295 timeout=self.options.timeout, verbose=self.options.debug)
1296 server.RedeemTicket(ticket_string, slice_cred)
1297 self.logger.info("Success")
1298 except socket.gaierror:
1299 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1300 except Exception, e:
1301 self.logger.log_exc(e.message)
1304 def create_gid(self, options, args):
1306 Create a GID (CreateGid)
1311 target_hrn = args[0]
1312 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1314 filename = options.file
1316 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1317 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1318 GID(string=gid).save_to_file(filename)
1321 def delegate(self, options, args):
1323 (locally) create delegate credential for use by given hrn
1325 delegee_hrn = args[0]
1326 if options.delegate_user:
1327 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1328 elif options.delegate_slice:
1329 slice_cred = self.slice_credential_string(options.delegate_slice)
1330 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1332 self.logger.warning("Must specify either --user or --slice <hrn>")
1334 delegated_cred = Credential(string=cred)
1335 object_hrn = delegated_cred.get_gid_object().get_hrn()
1336 if options.delegate_user:
1337 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1338 + get_leaf(object_hrn) + ".cred")
1339 elif options.delegate_slice:
1340 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1341 + get_leaf(object_hrn) + ".cred")
1343 delegated_cred.save_to_file(dest_fn, save_parents=True)
1345 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1347 def get_trusted_certs(self, options, args):
1349 return uhe trusted certs at this interface (get_trusted_certs)
1351 trusted_certs = self.registry().get_trusted_certs()
1352 for trusted_cert in trusted_certs:
1353 gid = GID(string=trusted_cert)
1355 cert = Certificate(string=trusted_cert)
1356 self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
1359 def config (self, options, args):
1360 "Display contents of current config"