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
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.sfalogging import sfi_logger
27 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn
28 from sfa.util.config import Config
29 from sfa.util.version import version_core
30 from sfa.util.cache import Cache
32 from sfa.storage.model import RegRecord, RegAuthority, RegUser, RegSlice, RegNode
33 from sfa.storage.model import make_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
48 def display_rspec(rspec, format='rspec'):
50 tree = etree.parse(StringIO(rspec))
52 result = root.xpath("./network/site/node/hostname/text()")
53 elif format in ['ip']:
54 # The IP address is not yet part of the new RSpec
55 # so this doesn't do anything yet.
56 tree = etree.parse(StringIO(rspec))
58 result = root.xpath("./network/site/node/ipv4/text()")
65 def display_list(results):
66 for result in results:
69 def display_records(recordList, dump=False):
70 ''' Print all fields in the record'''
71 for record in recordList:
72 display_record(record, dump)
74 def display_record(record, dump=False):
78 info = record.getdict()
79 print "%s (%s)" % (info['hrn'], info['type'])
83 def filter_records(type, records):
85 for record in records:
86 if (record['type'] == type) or (type == "all"):
87 filtered_records.append(record)
88 return filtered_records
92 def save_raw_to_file(var, filename, format="text", banner=None):
94 # if filename is "-", send it to stdout
97 f = open(filename, "w")
102 elif format == "pickled":
103 f.write(pickle.dumps(var))
104 elif format == "json":
105 if hasattr(json, "dumps"):
106 f.write(json.dumps(var)) # python 2.6
108 f.write(json.write(var)) # python 2.5
110 # this should never happen
111 print "unknown output format", format
113 f.write('\n'+banner+"\n")
115 def save_rspec_to_file(rspec, filename):
116 if not filename.endswith(".rspec"):
117 filename = filename + ".rspec"
118 f = open(filename, 'w')
123 def save_records_to_file(filename, record_dicts, format="xml"):
126 for record_dict in record_dicts:
128 save_record_to_file(filename + "." + str(index), record_dict)
130 save_record_to_file(filename, record_dict)
132 elif format == "xmllist":
133 f = open(filename, "w")
134 f.write("<recordlist>\n")
135 for record_dict in record_dicts:
136 record_obj=make_record (dict=record_dict)
137 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
138 f.write("</recordlist>\n")
140 elif format == "hrnlist":
141 f = open(filename, "w")
142 for record_dict in record_dicts:
143 record_obj=make_record (dict=record_dict)
144 f.write(record_obj.hrn + "\n")
147 # this should never happen
148 print "unknown output format", format
150 def save_record_to_file(filename, record_dict):
151 rec_record = make_record (dict=record_dict)
152 str = record.save_to_string()
153 f=codecs.open(filename, encoding='utf-8',mode="w")
160 def load_record_from_file(filename):
161 f=codecs.open(filename, encoding="utf-8", mode="r")
162 xml_string = f.read()
164 return make_record (xml=xml_string)
168 def unique_call_id(): return uuid.uuid4().urn
172 # dirty hack to make this class usable from the outside
173 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
176 def default_sfi_dir ():
177 if os.path.isfile("./sfi_config"):
180 return os.path.expanduser("~/.sfi/")
182 # dummy to meet Sfi's expectations for its 'options' field
183 # i.e. s/t we can do setattr on
187 def __init__ (self,options=None):
188 if options is None: options=Sfi.DummyOptions()
189 for opt in Sfi.required_options:
190 if not hasattr(options,opt): setattr(options,opt,None)
191 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
192 self.options = options
194 self.authority = None
195 self.logger = sfi_logger
196 self.logger.enable_console()
197 self.available_names = [ tuple[0] for tuple in Sfi.available ]
198 self.available_dict = dict (Sfi.available)
200 # tuples command-name expected-args in the order in which they should appear in the help
203 ("list", "authority"),
206 ("update", "record"),
209 ("resources", "[slice_hrn]"),
210 ("create", "slice_hrn rspec"),
211 ("delete", "slice_hrn"),
212 ("status", "slice_hrn"),
213 ("start", "slice_hrn"),
214 ("stop", "slice_hrn"),
215 ("reset", "slice_hrn"),
216 ("renew", "slice_hrn time"),
217 ("shutdown", "slice_hrn"),
218 ("get_ticket", "slice_hrn rspec"),
219 ("redeem_ticket", "ticket"),
220 ("delegate", "name"),
221 ("create_gid", "[name]"),
222 ("get_trusted_certs", "cred"),
225 def print_command_help (self, options):
226 verbose=getattr(options,'verbose')
227 format3="%18s %-15s %s"
230 print format3%("command","cmd_args","description")
234 self.create_parser().print_help()
235 for command in self.available_names:
236 args=self.available_dict[command]
237 method=getattr(self,command,None)
239 if method: doc=getattr(method,'__doc__',"")
240 if not doc: doc="*** no doc found ***"
241 doc=doc.strip(" \t\n")
242 doc=doc.replace("\n","\n"+35*' ')
245 print format3%(command,args,doc)
247 self.create_command_parser(command).print_help()
249 def create_command_parser(self, command):
250 if command not in self.available_dict:
251 msg="Invalid command\n"
253 msg += ','.join(self.available_names)
254 self.logger.critical(msg)
257 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
258 % (command, self.available_dict[command]))
260 # user specifies remote aggregate/sm/component
261 if command in ("resources", "slices", "create", "delete", "start", "stop",
262 "restart", "shutdown", "get_ticket", "renew", "status"):
263 parser.add_option("-d", "--delegate", dest="delegate", default=None,
265 help="Include a credential delegated to the user's root"+\
266 "authority in set of credentials for this call")
268 # registy filter option
269 if command in ("list", "show", "remove"):
270 parser.add_option("-t", "--type", dest="type", type="choice",
271 help="type filter ([all]|user|slice|authority|node|aggregate)",
272 choices=("all", "user", "slice", "authority", "node", "aggregate"),
274 if command in ("resources"):
276 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
277 help="schema type and version of resulting RSpec")
278 # disable/enable cached rspecs
279 parser.add_option("-c", "--current", dest="current", default=False,
281 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
283 parser.add_option("-f", "--format", dest="format", type="choice",
284 help="display format ([xml]|dns|ip)", default="xml",
285 choices=("xml", "dns", "ip"))
286 #panos: a new option to define the type of information about resources a user is interested in
287 parser.add_option("-i", "--info", dest="info",
288 help="optional component information", default=None)
291 # 'create' does return the new rspec, makes sense to save that too
292 if command in ("resources", "show", "list", "create_gid", 'create'):
293 parser.add_option("-o", "--output", dest="file",
294 help="output XML to file", metavar="FILE", default=None)
296 if command in ("show", "list"):
297 parser.add_option("-f", "--format", dest="format", type="choice",
298 help="display format ([text]|xml)", default="text",
299 choices=("text", "xml"))
301 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
302 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
303 choices=("xml", "xmllist", "hrnlist"))
305 if command in ("delegate"):
306 parser.add_option("-u", "--user",
307 action="store_true", dest="delegate_user", default=False,
308 help="delegate user credential")
309 parser.add_option("-s", "--slice", dest="delegate_slice",
310 help="delegate slice credential", metavar="HRN", default=None)
312 if command in ("version"):
313 parser.add_option("-R","--registry-version",
314 action="store_true", dest="version_registry", default=False,
315 help="probe registry version instead of sliceapi")
316 parser.add_option("-l","--local",
317 action="store_true", dest="version_local", default=False,
318 help="display version of the local client")
323 def create_parser(self):
325 # Generate command line parser
326 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
327 description="Commands: %s"%(" ".join(self.available_names)))
328 parser.add_option("-r", "--registry", dest="registry",
329 help="root registry", metavar="URL", default=None)
330 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
331 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
332 parser.add_option("-R", "--raw", dest="raw", default=None,
333 help="Save raw, unparsed server response to a file")
334 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
335 help="raw file format ([text]|pickled|json)", default="text",
336 choices=("text","pickled","json"))
337 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
338 help="text string to write before and after raw output")
339 parser.add_option("-d", "--dir", dest="sfi_dir",
340 help="config & working directory - default is %default",
341 metavar="PATH", default=Sfi.default_sfi_dir())
342 parser.add_option("-u", "--user", dest="user",
343 help="user name", metavar="HRN", default=None)
344 parser.add_option("-a", "--auth", dest="auth",
345 help="authority name", metavar="HRN", default=None)
346 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
347 help="verbose mode - cumulative")
348 parser.add_option("-D", "--debug",
349 action="store_true", dest="debug", default=False,
350 help="Debug (xml-rpc) protocol messages")
351 # would it make sense to use ~/.ssh/id_rsa as a default here ?
352 parser.add_option("-k", "--private-key",
353 action="store", dest="user_private_key", default=None,
354 help="point to the private key file to use if not yet installed in sfi_dir")
355 parser.add_option("-t", "--timeout", dest="timeout", default=None,
356 help="Amout of time to wait before timing out the request")
357 parser.add_option("-?", "--commands",
358 action="store_true", dest="command_help", default=False,
359 help="one page summary on commands & exit")
360 parser.disable_interspersed_args()
365 def print_help (self):
366 self.sfi_parser.print_help()
367 self.command_parser.print_help()
370 # Main: parse arguments and dispatch to command
372 def dispatch(self, command, command_options, command_args):
373 return getattr(self, command)(command_options, command_args)
376 self.sfi_parser = self.create_parser()
377 (options, args) = self.sfi_parser.parse_args()
378 if options.command_help:
379 self.print_command_help(options)
381 self.options = options
383 self.logger.setLevelFromOptVerbose(self.options.verbose)
386 self.logger.critical("No command given. Use -h for help.")
387 self.print_command_help(options)
391 self.command_parser = self.create_command_parser(command)
392 (command_options, command_args) = self.command_parser.parse_args(args[1:])
393 self.command_options = command_options
397 self.logger.info("Command=%s" % command)
400 self.dispatch(command, command_options, command_args)
402 self.logger.critical ("Unknown command %s"%command)
409 def read_config(self):
410 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
412 config = Config (config_file)
414 self.logger.critical("Failed to read configuration file %s"%config_file)
415 self.logger.info("Make sure to remove the export clauses and to add quotes")
416 if self.options.verbose==0:
417 self.logger.info("Re-run with -v for more details")
419 self.logger.log_exc("Could not read config file %s"%config_file)
424 if (self.options.sm is not None):
425 self.sm_url = self.options.sm
426 elif hasattr(config, "SFI_SM"):
427 self.sm_url = config.SFI_SM
429 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
433 if (self.options.registry is not None):
434 self.reg_url = self.options.registry
435 elif hasattr(config, "SFI_REGISTRY"):
436 self.reg_url = config.SFI_REGISTRY
438 self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
442 if (self.options.user is not None):
443 self.user = self.options.user
444 elif hasattr(config, "SFI_USER"):
445 self.user = config.SFI_USER
447 self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
451 if (self.options.auth is not None):
452 self.authority = self.options.auth
453 elif hasattr(config, "SFI_AUTH"):
454 self.authority = config.SFI_AUTH
456 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
463 # Get various credential and spec files
465 # Establishes limiting conventions
466 # - conflates MAs and SAs
467 # - assumes last token in slice name is unique
469 # Bootstraps credentials
470 # - bootstrap user credential from self-signed certificate
471 # - bootstrap authority credential from user credential
472 # - bootstrap slice credential from user credential
475 # init self-signed cert, user credentials and gid
476 def bootstrap (self):
477 bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
478 # if -k is provided, use this to initialize private key
479 if self.options.user_private_key:
480 bootstrap.init_private_key_if_missing (self.options.user_private_key)
482 # trigger legacy compat code if needed
483 # the name has changed from just <leaf>.pkey to <hrn>.pkey
484 if not os.path.isfile(bootstrap.private_key_filename()):
485 self.logger.info ("private key not found, trying legacy name")
487 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
488 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
489 bootstrap.init_private_key_if_missing (legacy_private_key)
490 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
492 self.logger.log_exc("Can't find private key ")
496 bootstrap.bootstrap_my_gid()
497 # extract what's needed
498 self.private_key = bootstrap.private_key()
499 self.my_credential_string = bootstrap.my_credential_string ()
500 self.my_gid = bootstrap.my_gid ()
501 self.bootstrap = bootstrap
504 def my_authority_credential_string(self):
505 if not self.authority:
506 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
508 return self.bootstrap.authority_credential_string (self.authority)
510 def slice_credential_string(self, name):
511 return self.bootstrap.slice_credential_string (name)
513 # xxx should be supported by sfaclientbootstrap as well
514 def delegate_cred(self, object_cred, hrn, type='authority'):
515 # the gid and hrn of the object we are delegating
516 if isinstance(object_cred, str):
517 object_cred = Credential(string=object_cred)
518 object_gid = object_cred.get_gid_object()
519 object_hrn = object_gid.get_hrn()
521 if not object_cred.get_privileges().get_all_delegate():
522 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
525 # the delegating user's gid
526 caller_gidfile = self.my_gid()
528 # the gid of the user who will be delegated to
529 delegee_gid = self.bootstrap.gid(hrn,type)
530 delegee_hrn = delegee_gid.get_hrn()
531 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
532 return dcred.save_to_string(save_parents=True)
535 # Management of the servers
540 if not hasattr (self, 'registry_proxy'):
541 self.logger.info("Contacting Registry at: %s"%self.reg_url)
542 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
543 timeout=self.options.timeout, verbose=self.options.debug)
544 return self.registry_proxy
548 if not hasattr (self, 'sliceapi_proxy'):
549 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
550 if hasattr(self.command_options,'component') and self.command_options.component:
551 # resolve the hrn at the registry
552 node_hrn = self.command_options.component
553 records = self.registry().Resolve(node_hrn, self.my_credential_string)
554 records = filter_records('node', records)
556 self.logger.warning("No such component:%r"% opts.component)
558 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
559 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
561 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
562 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
563 self.sm_url = 'http://' + self.sm_url
564 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
565 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
566 timeout=self.options.timeout, verbose=self.options.debug)
567 return self.sliceapi_proxy
569 def get_cached_server_version(self, server):
570 # check local cache first
573 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
574 cache_key = server.url + "-version"
576 cache = Cache(cache_file)
579 self.logger.info("Local cache not found at: %s" % cache_file)
582 version = cache.get(cache_key)
585 result = server.GetVersion()
586 version= ReturnValue.get_value(result)
587 # cache version for 20 minutes
588 cache.add(cache_key, version, ttl= 60*20)
589 self.logger.info("Updating cache file %s" % cache_file)
590 cache.save_to_file(cache_file)
594 ### resurrect this temporarily so we can support V1 aggregates for a while
595 def server_supports_options_arg(self, server):
597 Returns true if server support the optional call_id arg, false otherwise.
599 server_version = self.get_cached_server_version(server)
601 # xxx need to rewrite this
602 if int(server_version.get('geni_api')) >= 2:
606 def server_supports_call_id_arg(self, server):
607 server_version = self.get_cached_server_version(server)
609 if 'sfa' in server_version and 'code_tag' in server_version:
610 code_tag = server_version['code_tag']
611 code_tag_parts = code_tag.split("-")
612 version_parts = code_tag_parts[0].split(".")
613 major, minor = version_parts[0], version_parts[1]
614 rev = code_tag_parts[1]
615 if int(major) == 1 and minor == 0 and build >= 22:
619 ### ois = options if supported
620 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
621 def ois (self, server, option_dict):
622 if self.server_supports_options_arg (server):
624 elif self.server_supports_call_id_arg (server):
625 return [ unique_call_id () ]
629 ### cis = call_id if supported - like ois
630 def cis (self, server):
631 if self.server_supports_call_id_arg (server):
632 return [ unique_call_id ]
636 ######################################## miscell utilities
637 def get_rspec_file(self, rspec):
638 if (os.path.isabs(rspec)):
641 file = os.path.join(self.options.sfi_dir, rspec)
642 if (os.path.isfile(file)):
645 self.logger.critical("No such rspec file %s"%rspec)
648 def get_record_file(self, record):
649 if (os.path.isabs(record)):
652 file = os.path.join(self.options.sfi_dir, record)
653 if (os.path.isfile(file)):
656 self.logger.critical("No such registry record file %s"%record)
660 #==========================================================================
661 # Following functions implement the commands
663 # Registry-related commands
664 #==========================================================================
666 def version(self, options, args):
668 display an SFA server version (GetVersion)
669 or version information about sfi itself
671 if options.version_local:
672 version=version_core()
674 if options.version_registry:
675 server=self.registry()
677 server = self.sliceapi()
678 result = server.GetVersion()
679 version = ReturnValue.get_value(result)
681 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
683 pprinter = PrettyPrinter(indent=4)
684 pprinter.pprint(version)
686 def list(self, options, args):
688 list entries in named authority registry (List)
695 list = self.registry().List(hrn, self.my_credential_string)
697 raise Exception, "Not enough parameters for the 'list' command"
699 # filter on person, slice, site, node, etc.
700 # THis really should be in the self.filter_records funct def comment...
701 list = filter_records(options.type, list)
703 print "%s (%s)" % (record['hrn'], record['type'])
705 save_records_to_file(options.file, list, options.fileformat)
708 def show(self, options, args):
710 show details about named registry record (Resolve)
716 record_dicts = self.registry().Resolve(hrn, self.my_credential_string)
717 record_dicts = filter_records(options.type, record_dicts)
719 self.logger.error("No record of type %s"% options.type)
720 records = [ make_record (dict=record_dict) for record_dict in record_dicts ]
721 for record in records:
722 if (options.format == "text"): record.dump()
723 else: print record.save_as_xml()
725 save_records_to_file(options.file, record_dicts, options.fileformat)
728 def add(self, options, args):
729 "add record into registry from xml file (Register)"
730 auth_cred = self.my_authority_credential_string()
734 record_filepath = args[0]
735 rec_file = self.get_record_file(record_filepath)
736 record = load_record_from_file(rec_file).todict()
737 return self.registry().Register(record, auth_cred)
739 def update(self, options, args):
740 "update record into registry from xml file (Update)"
744 rec_file = self.get_record_file(args[0])
745 record = load_record_from_file(rec_file)
746 if record.type == "user":
747 if record.hrn == self.user:
748 cred = self.my_credential_string
750 cred = self.my_authority_credential_string()
751 elif record.type in ["slice"]:
753 cred = self.slice_credential_string(record.hrn)
754 except ServerException, e:
755 # XXX smbaker -- once we have better error return codes, update this
756 # to do something better than a string compare
757 if "Permission error" in e.args[0]:
758 cred = self.my_authority_credential_string()
761 elif record.type in ["authority"]:
762 cred = self.my_authority_credential_string()
763 elif record.type == 'node':
764 cred = self.my_authority_credential_string()
766 raise "unknown record type" + record.type
767 record_dict = record.todict()
768 return self.registry().Update(record_dict, cred)
770 def remove(self, options, args):
771 "remove registry record by name (Remove)"
772 auth_cred = self.my_authority_credential_string()
780 return self.registry().Remove(hrn, auth_cred, type)
782 # ==================================================================
783 # Slice-related commands
784 # ==================================================================
786 def slices(self, options, args):
787 "list instantiated slices (ListSlices) - returns urn's"
788 server = self.sliceapi()
790 creds = [self.my_credential_string]
792 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
793 creds.append(delegated_cred)
794 # options and call_id when supported
796 api_options['call_id']=unique_call_id()
797 result = server.ListSlices(creds, *self.ois(server,api_options))
798 value = ReturnValue.get_value(result)
800 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
805 # show rspec for named slice
806 def resources(self, options, args):
808 with no arg, discover available resources, (ListResources)
809 or with an slice hrn, shows currently provisioned resources
811 server = self.sliceapi()
816 creds.append(self.slice_credential_string(args[0]))
818 creds.append(self.my_credential_string)
820 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
822 # no need to check if server accepts the options argument since the options has
823 # been a required argument since v1 API
825 # always send call_id to v2 servers
826 api_options ['call_id'] = unique_call_id()
827 # ask for cached value if available
828 api_options ['cached'] = True
831 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
833 api_options['info'] = options.info
835 if options.current == True:
836 api_options['cached'] = False
838 api_options['cached'] = True
839 if options.rspec_version:
840 version_manager = VersionManager()
841 server_version = self.get_cached_server_version(server)
842 if 'sfa' in server_version:
843 # just request the version the client wants
844 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
846 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
848 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
849 result = server.ListResources (creds, api_options)
850 value = ReturnValue.get_value(result)
852 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
853 if options.file is not None:
854 save_rspec_to_file(value, options.file)
855 if (self.options.raw is None) and (options.file is None):
856 display_rspec(value, options.format)
860 def create(self, options, args):
862 create or update named slice with given rspec
864 server = self.sliceapi()
866 # xxx do we need to check usage (len(args)) ?
869 slice_urn = hrn_to_urn(slice_hrn, 'slice')
872 creds = [self.slice_credential_string(slice_hrn)]
873 delegated_cred = None
874 server_version = self.get_cached_server_version(server)
875 if server_version.get('interface') == 'slicemgr':
876 # delegate our cred to the slice manager
877 # do not delegate cred to slicemgr...not working at the moment
879 #if server_version.get('hrn'):
880 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
881 #elif server_version.get('urn'):
882 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
885 rspec_file = self.get_rspec_file(args[1])
886 rspec = open(rspec_file).read()
889 # need to pass along user keys to the aggregate.
891 # { urn: urn:publicid:IDN+emulab.net+user+alice
892 # keys: [<ssh key A>, <ssh key B>]
895 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
896 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
897 slice_record = slice_records[0]
898 user_hrns = slice_record['researcher']
899 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
900 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
902 if 'sfa' not in server_version:
903 users = pg_users_arg(user_records)
905 rspec.filter({'component_manager_id': server_version['urn']})
906 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
908 users = sfa_users_arg(user_records, slice_record)
910 # do not append users, keys, or slice tags. Anything
911 # not contained in this request will be removed from the slice
913 # CreateSliver has supported the options argument for a while now so it should
914 # be safe to assume this server support it
916 api_options ['append'] = False
917 api_options ['call_id'] = unique_call_id()
919 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
920 value = ReturnValue.get_value(result)
922 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
923 if options.file is not None:
924 save_rspec_to_file (value, options.file)
925 if (self.options.raw is None) and (options.file is None):
930 def delete(self, options, args):
932 delete named slice (DeleteSliver)
934 server = self.sliceapi()
938 slice_urn = hrn_to_urn(slice_hrn, 'slice')
941 slice_cred = self.slice_credential_string(slice_hrn)
944 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
945 creds.append(delegated_cred)
947 # options and call_id when supported
949 api_options ['call_id'] = unique_call_id()
950 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
951 value = ReturnValue.get_value(result)
953 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
958 def status(self, options, args):
960 retrieve slice status (SliverStatus)
962 server = self.sliceapi()
966 slice_urn = hrn_to_urn(slice_hrn, 'slice')
969 slice_cred = self.slice_credential_string(slice_hrn)
972 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
973 creds.append(delegated_cred)
975 # options and call_id when supported
977 api_options['call_id']=unique_call_id()
978 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
979 value = ReturnValue.get_value(result)
981 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
985 def start(self, options, args):
987 start named slice (Start)
989 server = self.sliceapi()
993 slice_urn = hrn_to_urn(slice_hrn, 'slice')
996 slice_cred = self.slice_credential_string(args[0])
999 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1000 creds.append(delegated_cred)
1001 # xxx Thierry - does this not need an api_options as well ?
1002 result = server.Start(slice_urn, creds)
1003 value = ReturnValue.get_value(result)
1004 if self.options.raw:
1005 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1010 def stop(self, options, args):
1012 stop named slice (Stop)
1014 server = self.sliceapi()
1017 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1019 slice_cred = self.slice_credential_string(args[0])
1020 creds = [slice_cred]
1021 if options.delegate:
1022 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1023 creds.append(delegated_cred)
1024 result = server.Stop(slice_urn, creds)
1025 value = ReturnValue.get_value(result)
1026 if self.options.raw:
1027 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1033 def reset(self, options, args):
1035 reset named slice (reset_slice)
1037 server = self.sliceapi()
1040 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1042 slice_cred = self.slice_credential_string(args[0])
1043 creds = [slice_cred]
1044 if options.delegate:
1045 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1046 creds.append(delegated_cred)
1047 result = server.reset_slice(creds, slice_urn)
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)
1055 def renew(self, options, args):
1057 renew slice (RenewSliver)
1059 server = self.sliceapi()
1062 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1064 slice_cred = self.slice_credential_string(args[0])
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)
1071 # options and call_id when supported
1073 api_options['call_id']=unique_call_id()
1074 result = server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1075 value = ReturnValue.get_value(result)
1076 if self.options.raw:
1077 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1083 def shutdown(self, options, args):
1085 shutdown named slice (Shutdown)
1087 server = self.sliceapi()
1090 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)
1097 result = server.Shutdown(slice_urn, creds)
1098 value = ReturnValue.get_value(result)
1099 if self.options.raw:
1100 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1106 def get_ticket(self, options, args):
1108 get a ticket for the specified slice
1110 server = self.sliceapi()
1112 slice_hrn, rspec_path = args[0], args[1]
1113 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1115 slice_cred = self.slice_credential_string(slice_hrn)
1116 creds = [slice_cred]
1117 if options.delegate:
1118 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1119 creds.append(delegated_cred)
1121 rspec_file = self.get_rspec_file(rspec_path)
1122 rspec = open(rspec_file).read()
1123 # options and call_id when supported
1125 api_options['call_id']=unique_call_id()
1126 # get ticket at the server
1127 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1129 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1130 self.logger.info("writing ticket to %s"%file)
1131 ticket = SfaTicket(string=ticket_string)
1132 ticket.save_to_file(filename=file, save_parents=True)
1134 def redeem_ticket(self, options, args):
1136 Connects to nodes in a slice and redeems a ticket
1137 (slice hrn is retrieved from the ticket)
1139 ticket_file = args[0]
1141 # get slice hrn from the ticket
1142 # use this to get the right slice credential
1143 ticket = SfaTicket(filename=ticket_file)
1145 ticket_string = ticket.save_to_string(save_parents=True)
1147 slice_hrn = ticket.gidObject.get_hrn()
1148 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1149 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1150 slice_cred = self.slice_credential_string(slice_hrn)
1152 # get a list of node hostnames from the RSpec
1153 tree = etree.parse(StringIO(ticket.rspec))
1154 root = tree.getroot()
1155 hostnames = root.xpath("./network/site/node/hostname/text()")
1157 # create an xmlrpc connection to the component manager at each of these
1158 # components and gall redeem_ticket
1160 for hostname in hostnames:
1162 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1163 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1164 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1165 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1166 timeout=self.options.timeout, verbose=self.options.debug)
1167 server.RedeemTicket(ticket_string, slice_cred)
1168 self.logger.info("Success")
1169 except socket.gaierror:
1170 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1171 except Exception, e:
1172 self.logger.log_exc(e.message)
1175 def create_gid(self, options, args):
1177 Create a GID (CreateGid)
1182 target_hrn = args[0]
1183 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.bootstrap.my_gid_string())
1185 filename = options.file
1187 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1188 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1189 GID(string=gid).save_to_file(filename)
1192 def delegate(self, options, args):
1194 (locally) create delegate credential for use by given hrn
1196 delegee_hrn = args[0]
1197 if options.delegate_user:
1198 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1199 elif options.delegate_slice:
1200 slice_cred = self.slice_credential_string(options.delegate_slice)
1201 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1203 self.logger.warning("Must specify either --user or --slice <hrn>")
1205 delegated_cred = Credential(string=cred)
1206 object_hrn = delegated_cred.get_gid_object().get_hrn()
1207 if options.delegate_user:
1208 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1209 + get_leaf(object_hrn) + ".cred")
1210 elif options.delegate_slice:
1211 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1212 + get_leaf(object_hrn) + ".cred")
1214 delegated_cred.save_to_file(dest_fn, save_parents=True)
1216 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1218 def get_trusted_certs(self, options, args):
1220 return uhe trusted certs at this interface (get_trusted_certs)
1222 trusted_certs = self.registry().get_trusted_certs()
1223 for trusted_cert in trusted_certs:
1224 gid = GID(string=trusted_cert)
1226 cert = Certificate(string=trusted_cert)
1227 self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())