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
15 from lxml import etree
16 from StringIO import StringIO
17 from optparse import OptionParser
18 from pprint import PrettyPrinter
20 from sfa.trust.certificate import Keypair, Certificate
21 from sfa.trust.gid import GID
22 from sfa.trust.credential import Credential
23 from sfa.trust.sfaticket import SfaTicket
25 from sfa.util.sfalogging import sfi_logger
26 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn
27 from sfa.util.config import Config
28 from sfa.util.version import version_core
29 from sfa.util.cache import Cache
31 #from sfa.storage.record import SfaRecord, UserRecord, SliceRecord, NodeRecord, AuthorityRecord
32 from sfa.storage.persistentobjs import RegRecord
34 from sfa.rspecs.rspec import RSpec
35 from sfa.rspecs.rspec_converter import RSpecConverter
36 from sfa.rspecs.version_manager import VersionManager
38 from sfa.client.sfaclientlib import SfaClientBootstrap
39 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
40 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
41 from sfa.client.return_value import ReturnValue
45 # utility methods here
47 def display_rspec(rspec, format='rspec'):
49 tree = etree.parse(StringIO(rspec))
51 result = root.xpath("./network/site/node/hostname/text()")
52 elif format in ['ip']:
53 # The IP address is not yet part of the new RSpec
54 # so this doesn't do anything yet.
55 tree = etree.parse(StringIO(rspec))
57 result = root.xpath("./network/site/node/ipv4/text()")
64 def display_list(results):
65 for result in results:
68 def display_records(recordList, dump=False):
69 ''' Print all fields in the record'''
70 for record in recordList:
71 display_record(record, dump)
73 def display_record(record, dump=False):
77 info = record.getdict()
78 print "%s (%s)" % (info['hrn'], info['type'])
82 def filter_records(type, records):
84 for record in records:
85 if (record['type'] == type) or (type == "all"):
86 filtered_records.append(record)
87 return filtered_records
91 def save_variable_to_file(var, filename, format="text"):
92 f = open(filename, "w")
95 elif format == "pickled":
96 f.write(pickle.dumps(var))
98 # this should never happen
99 print "unknown output format", format
102 def save_rspec_to_file(rspec, filename):
103 if not filename.endswith(".rspec"):
104 filename = filename + ".rspec"
105 f = open(filename, 'w')
110 def save_records_to_file(filename, recordList, format="xml"):
113 for record in recordList:
115 save_record_to_file(filename + "." + str(index), record)
117 save_record_to_file(filename, record)
119 elif format == "xmllist":
120 f = open(filename, "w")
121 f.write("<recordlist>\n")
122 for record in recordList:
123 record_obj=RegRecord (dict=record)
124 f.write('<record hrn="' + record.hrn + '" type="' + record.type + '" />\n')
125 f.write("</recordlist>\n")
127 elif format == "hrnlist":
128 f = open(filename, "w")
129 for record in recordList:
130 record_obj=RegRecord (dict=record)
131 f.write(record.hrn + "\n")
134 # this should never happen
135 print "unknown output format", format
137 def save_record_to_file(filename, record):
138 if record['type'] in ['user']:
140 record = RegRecord(dict=record)
141 elif record['type'] in ['slice']:
143 record = RegRecord(dict=record)
144 elif record['type'] in ['node']:
146 record = RegRecord(dict=record)
147 elif record['type'] in ['authority', 'ma', 'sa']:
149 record = RegRecord(dict=record)
151 record = RegRecord(dict=record)
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")
165 record.load_from_xml(str)
170 def unique_call_id(): return uuid.uuid4().urn
174 # dirty hack to make this class usable from the outside
175 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
178 def default_sfi_dir ():
179 if os.path.isfile("./sfi_config"):
182 return os.path.expanduser("~/.sfi/")
184 # dummy to meet Sfi's expectations for its 'options' field
185 # i.e. s/t we can do setattr on
189 def __init__ (self,options=None):
190 if options is None: options=Sfi.DummyOptions()
191 for opt in Sfi.required_options:
192 if not hasattr(options,opt): setattr(options,opt,None)
193 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
194 self.options = options
196 self.authority = None
197 self.logger = sfi_logger
198 self.logger.enable_console()
199 self.available_names = [ tuple[0] for tuple in Sfi.available ]
200 self.available_dict = dict (Sfi.available)
202 # tuples command-name expected-args in the order in which they should appear in the help
205 ("list", "authority"),
208 ("update", "record"),
211 ("resources", "[slice_hrn]"),
212 ("create", "slice_hrn rspec"),
213 ("delete", "slice_hrn"),
214 ("status", "slice_hrn"),
215 ("start", "slice_hrn"),
216 ("stop", "slice_hrn"),
217 ("reset", "slice_hrn"),
218 ("renew", "slice_hrn time"),
219 ("shutdown", "slice_hrn"),
220 ("get_ticket", "slice_hrn rspec"),
221 ("redeem_ticket", "ticket"),
222 ("delegate", "name"),
223 ("create_gid", "[name]"),
224 ("get_trusted_certs", "cred"),
227 def print_command_help (self, options):
228 verbose=getattr(options,'verbose')
229 format3="%18s %-15s %s"
232 print format3%("command","cmd_args","description")
236 self.create_parser().print_help()
237 for command in self.available_names:
238 args=self.available_dict[command]
239 method=getattr(self,command,None)
241 if method: doc=getattr(method,'__doc__',"")
242 if not doc: doc="*** no doc found ***"
243 doc=doc.strip(" \t\n")
244 doc=doc.replace("\n","\n"+35*' ')
247 print format3%(command,args,doc)
249 self.create_command_parser(command).print_help()
251 def create_command_parser(self, command):
252 if command not in self.available_dict:
253 msg="Invalid command\n"
255 msg += ','.join(self.available_names)
256 self.logger.critical(msg)
259 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
260 % (command, self.available_dict[command]))
262 # user specifies remote aggregate/sm/component
263 if command in ("resources", "slices", "create", "delete", "start", "stop",
264 "restart", "shutdown", "get_ticket", "renew", "status"):
265 parser.add_option("-d", "--delegate", dest="delegate", default=None,
267 help="Include a credential delegated to the user's root"+\
268 "authority in set of credentials for this call")
270 # registy filter option
271 if command in ("list", "show", "remove"):
272 parser.add_option("-t", "--type", dest="type", type="choice",
273 help="type filter ([all]|user|slice|authority|node|aggregate)",
274 choices=("all", "user", "slice", "authority", "node", "aggregate"),
276 if command in ("resources"):
278 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
279 help="schema type and version of resulting RSpec")
280 # disable/enable cached rspecs
281 parser.add_option("-c", "--current", dest="current", default=False,
283 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
285 parser.add_option("-f", "--format", dest="format", type="choice",
286 help="display format ([xml]|dns|ip)", default="xml",
287 choices=("xml", "dns", "ip"))
288 #panos: a new option to define the type of information about resources a user is interested in
289 parser.add_option("-i", "--info", dest="info",
290 help="optional component information", default=None)
293 # 'create' does return the new rspec, makes sense to save that too
294 if command in ("resources", "show", "list", "create_gid", 'create'):
295 parser.add_option("-o", "--output", dest="file",
296 help="output XML to file", metavar="FILE", default=None)
298 if command in ("show", "list"):
299 parser.add_option("-f", "--format", dest="format", type="choice",
300 help="display format ([text]|xml)", default="text",
301 choices=("text", "xml"))
303 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
304 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
305 choices=("xml", "xmllist", "hrnlist"))
307 if command in ("status", "version"):
308 parser.add_option("-o", "--output", dest="file",
309 help="output dictionary to file", metavar="FILE", default=None)
310 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
311 help="output file format ([text]|pickled)", default="text",
312 choices=("text","pickled"))
314 if command in ("delegate"):
315 parser.add_option("-u", "--user",
316 action="store_true", dest="delegate_user", default=False,
317 help="delegate user credential")
318 parser.add_option("-s", "--slice", dest="delegate_slice",
319 help="delegate slice credential", metavar="HRN", default=None)
321 if command in ("version"):
322 parser.add_option("-R","--registry-version",
323 action="store_true", dest="version_registry", default=False,
324 help="probe registry version instead of sliceapi")
325 parser.add_option("-l","--local",
326 action="store_true", dest="version_local", default=False,
327 help="display version of the local client")
332 def create_parser(self):
334 # Generate command line parser
335 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
336 description="Commands: %s"%(" ".join(self.available_names)))
337 parser.add_option("-r", "--registry", dest="registry",
338 help="root registry", metavar="URL", default=None)
339 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
340 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
341 parser.add_option("-d", "--dir", dest="sfi_dir",
342 help="config & working directory - default is %default",
343 metavar="PATH", default=Sfi.default_sfi_dir())
344 parser.add_option("-u", "--user", dest="user",
345 help="user name", metavar="HRN", default=None)
346 parser.add_option("-a", "--auth", dest="auth",
347 help="authority name", metavar="HRN", default=None)
348 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
349 help="verbose mode - cumulative")
350 parser.add_option("-D", "--debug",
351 action="store_true", dest="debug", default=False,
352 help="Debug (xml-rpc) protocol messages")
353 # would it make sense to use ~/.ssh/id_rsa as a default here ?
354 parser.add_option("-k", "--private-key",
355 action="store", dest="user_private_key", default=None,
356 help="point to the private key file to use if not yet installed in sfi_dir")
357 parser.add_option("-t", "--timeout", dest="timeout", default=None,
358 help="Amout of time to wait before timing out the request")
359 parser.add_option("-?", "--commands",
360 action="store_true", dest="command_help", default=False,
361 help="one page summary on commands & exit")
362 parser.disable_interspersed_args()
367 def print_help (self):
368 self.sfi_parser.print_help()
369 self.command_parser.print_help()
372 # Main: parse arguments and dispatch to command
374 def dispatch(self, command, command_options, command_args):
375 return getattr(self, command)(command_options, command_args)
378 self.sfi_parser = self.create_parser()
379 (options, args) = self.sfi_parser.parse_args()
380 if options.command_help:
381 self.print_command_help(options)
383 self.options = options
385 self.logger.setLevelFromOptVerbose(self.options.verbose)
388 self.logger.critical("No command given. Use -h for help.")
389 self.print_command_help(options)
393 self.command_parser = self.create_command_parser(command)
394 (command_options, command_args) = self.command_parser.parse_args(args[1:])
395 self.command_options = command_options
399 self.logger.info("Command=%s" % command)
402 self.dispatch(command, command_options, command_args)
404 self.logger.critical ("Unknown command %s"%command)
411 def read_config(self):
412 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
414 config = Config (config_file)
416 self.logger.critical("Failed to read configuration file %s"%config_file)
417 self.logger.info("Make sure to remove the export clauses and to add quotes")
418 if self.options.verbose==0:
419 self.logger.info("Re-run with -v for more details")
421 self.logger.log_exc("Could not read config file %s"%config_file)
426 if (self.options.sm is not None):
427 self.sm_url = self.options.sm
428 elif hasattr(config, "SFI_SM"):
429 self.sm_url = config.SFI_SM
431 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
435 if (self.options.registry is not None):
436 self.reg_url = self.options.registry
437 elif hasattr(config, "SFI_REGISTRY"):
438 self.reg_url = config.SFI_REGISTRY
440 self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
444 if (self.options.user is not None):
445 self.user = self.options.user
446 elif hasattr(config, "SFI_USER"):
447 self.user = config.SFI_USER
449 self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
453 if (self.options.auth is not None):
454 self.authority = self.options.auth
455 elif hasattr(config, "SFI_AUTH"):
456 self.authority = config.SFI_AUTH
458 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
465 # Get various credential and spec files
467 # Establishes limiting conventions
468 # - conflates MAs and SAs
469 # - assumes last token in slice name is unique
471 # Bootstraps credentials
472 # - bootstrap user credential from self-signed certificate
473 # - bootstrap authority credential from user credential
474 # - bootstrap slice credential from user credential
477 # init self-signed cert, user credentials and gid
478 def bootstrap (self):
479 bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
480 # if -k is provided, use this to initialize private key
481 if self.options.user_private_key:
482 bootstrap.init_private_key_if_missing (self.options.user_private_key)
484 # trigger legacy compat code if needed
485 # the name has changed from just <leaf>.pkey to <hrn>.pkey
486 if not os.path.isfile(bootstrap.private_key_filename()):
487 self.logger.info ("private key not found, trying legacy name")
489 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
490 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
491 bootstrap.init_private_key_if_missing (legacy_private_key)
492 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
494 self.logger.log_exc("Can't find private key ")
498 bootstrap.bootstrap_my_gid()
499 # extract what's needed
500 self.private_key = bootstrap.private_key()
501 self.my_credential_string = bootstrap.my_credential_string ()
502 self.my_gid = bootstrap.my_gid ()
503 self.bootstrap = bootstrap
506 def my_authority_credential_string(self):
507 if not self.authority:
508 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
510 return self.bootstrap.authority_credential_string (self.authority)
512 def slice_credential_string(self, name):
513 return self.bootstrap.slice_credential_string (name)
515 # xxx should be supported by sfaclientbootstrap as well
516 def delegate_cred(self, object_cred, hrn, type='authority'):
517 # the gid and hrn of the object we are delegating
518 if isinstance(object_cred, str):
519 object_cred = Credential(string=object_cred)
520 object_gid = object_cred.get_gid_object()
521 object_hrn = object_gid.get_hrn()
523 if not object_cred.get_privileges().get_all_delegate():
524 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
527 # the delegating user's gid
528 caller_gidfile = self.my_gid()
530 # the gid of the user who will be delegated to
531 delegee_gid = self.bootstrap.gid(hrn,type)
532 delegee_hrn = delegee_gid.get_hrn()
533 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
534 return dcred.save_to_string(save_parents=True)
537 # Management of the servers
542 if not hasattr (self, 'registry_proxy'):
543 self.logger.info("Contacting Registry at: %s"%self.reg_url)
544 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
545 timeout=self.options.timeout, verbose=self.options.debug)
546 return self.registry_proxy
550 if not hasattr (self, 'sliceapi_proxy'):
551 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
552 if hasattr(self.command_options,'component') and self.command_options.component:
553 # resolve the hrn at the registry
554 node_hrn = self.command_options.component
555 records = self.registry().Resolve(node_hrn, self.my_credential_string)
556 records = filter_records('node', records)
558 self.logger.warning("No such component:%r"% opts.component)
560 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
561 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
563 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
564 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
565 self.sm_url = 'http://' + self.sm_url
566 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
567 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
568 timeout=self.options.timeout, verbose=self.options.debug)
569 return self.sliceapi_proxy
571 def get_cached_server_version(self, server):
572 # check local cache first
575 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
576 cache_key = server.url + "-version"
578 cache = Cache(cache_file)
581 self.logger.info("Local cache not found at: %s" % cache_file)
584 version = cache.get(cache_key)
587 result = server.GetVersion()
588 version= ReturnValue.get_value(result)
589 # cache version for 20 minutes
590 cache.add(cache_key, version, ttl= 60*20)
591 self.logger.info("Updating cache file %s" % cache_file)
592 cache.save_to_file(cache_file)
596 ### resurrect this temporarily so we can support V1 aggregates for a while
597 def server_supports_options_arg(self, server):
599 Returns true if server support the optional call_id arg, false otherwise.
601 server_version = self.get_cached_server_version(server)
603 # xxx need to rewrite this
604 if int(server_version.get('geni_api')) >= 2:
608 def server_supports_call_id_arg(self, server):
609 server_version = self.get_cached_server_version(server)
611 if 'sfa' in server_version and 'code_tag' in server_version:
612 code_tag = server_version['code_tag']
613 code_tag_parts = code_tag.split("-")
614 version_parts = code_tag_parts[0].split(".")
615 major, minor = version_parts[0], version_parts[1]
616 rev = code_tag_parts[1]
617 if int(major) == 1 and minor == 0 and build >= 22:
621 ### ois = options if supported
622 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
623 def ois (self, server, option_dict):
624 if self.server_supports_options_arg (server):
626 elif self.server_supports_call_id_arg (server):
627 return [ unique_call_id () ]
631 ### cis = call_id if supported - like ois
632 def cis (self, server):
633 if self.server_supports_call_id_arg (server):
634 return [ unique_call_id ]
638 ######################################## miscell utilities
639 def get_rspec_file(self, rspec):
640 if (os.path.isabs(rspec)):
643 file = os.path.join(self.options.sfi_dir, rspec)
644 if (os.path.isfile(file)):
647 self.logger.critical("No such rspec file %s"%rspec)
650 def get_record_file(self, record):
651 if (os.path.isabs(record)):
654 file = os.path.join(self.options.sfi_dir, record)
655 if (os.path.isfile(file)):
658 self.logger.critical("No such registry record file %s"%record)
662 #==========================================================================
663 # Following functions implement the commands
665 # Registry-related commands
666 #==========================================================================
668 def version(self, options, args):
670 display an SFA server version (GetVersion)
671 or version information about sfi itself
673 if options.version_local:
674 version=version_core()
676 if options.version_registry:
677 server=self.registry()
679 server = self.sliceapi()
680 result = server.GetVersion()
681 version = ReturnValue.get_value(result)
682 pprinter = PrettyPrinter(indent=4)
683 pprinter.pprint(version)
685 save_variable_to_file(version, options.file, options.fileformat)
687 def list(self, options, args):
689 list entries in named authority registry (List)
696 list = self.registry().List(hrn, self.my_credential_string)
698 raise Exception, "Not enough parameters for the 'list' command"
700 # filter on person, slice, site, node, etc.
701 # THis really should be in the self.filter_records funct def comment...
702 list = filter_records(options.type, list)
704 print "%s (%s)" % (record['hrn'], record['type'])
706 save_records_to_file(options.file, list, options.fileformat)
709 def show(self, options, args):
711 show details about named registry record (Resolve)
717 records = self.registry().Resolve(hrn, self.my_credential_string)
718 records = filter_records(options.type, records)
720 self.logger.error("No record of type %s"% options.type)
721 for record in records:
722 if record['type'] in ['user']:
724 record = RegRecord(dict=record)
725 elif record['type'] in ['slice']:
727 record = RegRecord(dict=record)
728 elif record['type'] in ['node']:
730 record = RegRecord(dict=record)
731 elif record['type'].startswith('authority'):
733 record = RegRecord(dict=record)
735 record = RegRecord(dict=record)
737 if (options.format == "text"):
740 print record.save_as_xml()
742 save_records_to_file(options.file, records, options.fileformat)
745 def add(self, options, args):
746 "add record into registry from xml file (Register)"
747 auth_cred = self.my_authority_credential_string()
751 record_filepath = args[0]
752 rec_file = self.get_record_file(record_filepath)
753 record = load_record_from_file(rec_file).todict()
754 return self.registry().Register(record, auth_cred)
756 def update(self, options, args):
757 "update record into registry from xml file (Update)"
761 rec_file = self.get_record_file(args[0])
762 record = load_record_from_file(rec_file)
763 if record['type'] == "user":
764 if record.get_name() == self.user:
765 cred = self.my_credential_string
767 cred = self.my_authority_credential_string()
768 elif record['type'] in ["slice"]:
770 cred = self.slice_credential_string(record.get_name())
771 except ServerException, e:
772 # XXX smbaker -- once we have better error return codes, update this
773 # to do something better than a string compare
774 if "Permission error" in e.args[0]:
775 cred = self.my_authority_credential_string()
778 elif record.get_type() in ["authority"]:
779 cred = self.my_authority_credential_string()
780 elif record.get_type() == 'node':
781 cred = self.my_authority_credential_string()
783 raise "unknown record type" + record.get_type()
784 record = record.as_dict()
785 return self.registry().Update(record, cred)
787 def remove(self, options, args):
788 "remove registry record by name (Remove)"
789 auth_cred = self.my_authority_credential_string()
797 return self.registry().Remove(hrn, auth_cred, type)
799 # ==================================================================
800 # Slice-related commands
801 # ==================================================================
803 def slices(self, options, args):
804 "list instantiated slices (ListSlices) - returns urn's"
805 server = self.sliceapi()
807 creds = [self.my_credential_string]
809 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
810 creds.append(delegated_cred)
811 # options and call_id when supported
813 api_options['call_id']=unique_call_id()
814 result = server.ListSlices(creds, *self.ois(server,api_options))
815 value = ReturnValue.get_value(result)
819 # show rspec for named slice
820 def resources(self, options, args):
822 with no arg, discover available resources, (ListResources)
823 or with an slice hrn, shows currently provisioned resources
825 server = self.sliceapi()
830 creds.append(self.slice_credential_string(args[0]))
832 creds.append(self.my_credential_string)
834 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
836 # no need to check if server accepts the options argument since the options has
837 # been a required argument since v1 API
839 # always send call_id to v2 servers
840 api_options ['call_id'] = unique_call_id()
841 # ask for cached value if available
842 api_options ['cached'] = True
845 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
847 api_options['info'] = options.info
849 if options.current == True:
850 api_options['cached'] = False
852 api_options['cached'] = True
853 if options.rspec_version:
854 version_manager = VersionManager()
855 server_version = self.get_cached_server_version(server)
856 if 'sfa' in server_version:
857 # just request the version the client wants
858 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
860 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
862 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
863 result = server.ListResources (creds, api_options)
864 value = ReturnValue.get_value(result)
865 if options.file is None:
866 display_rspec(value, options.format)
868 save_rspec_to_file(value, options.file)
871 def create(self, options, args):
873 create or update named slice with given rspec
875 server = self.sliceapi()
877 # xxx do we need to check usage (len(args)) ?
880 slice_urn = hrn_to_urn(slice_hrn, 'slice')
883 creds = [self.slice_credential_string(slice_hrn)]
884 delegated_cred = None
885 server_version = self.get_cached_server_version(server)
886 if server_version.get('interface') == 'slicemgr':
887 # delegate our cred to the slice manager
888 # do not delegate cred to slicemgr...not working at the moment
890 #if server_version.get('hrn'):
891 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
892 #elif server_version.get('urn'):
893 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
896 rspec_file = self.get_rspec_file(args[1])
897 rspec = open(rspec_file).read()
900 # need to pass along user keys to the aggregate.
902 # { urn: urn:publicid:IDN+emulab.net+user+alice
903 # keys: [<ssh key A>, <ssh key B>]
906 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
907 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
908 slice_record = slice_records[0]
909 user_hrns = slice_record['researcher']
910 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
911 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
913 if 'sfa' not in server_version:
914 users = pg_users_arg(user_records)
916 rspec.filter({'component_manager_id': server_version['urn']})
917 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
919 users = sfa_users_arg(user_records, slice_record)
921 # do not append users, keys, or slice tags. Anything
922 # not contained in this request will be removed from the slice
924 # CreateSliver has supported the options argument for a while now so it should
925 # be safe to assume this server support it
927 api_options ['append'] = False
928 api_options ['call_id'] = unique_call_id()
930 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
931 value = ReturnValue.get_value(result)
932 if options.file is None:
935 save_rspec_to_file (value, options.file)
938 def delete(self, options, args):
940 delete named slice (DeleteSliver)
942 server = self.sliceapi()
946 slice_urn = hrn_to_urn(slice_hrn, 'slice')
949 slice_cred = self.slice_credential_string(slice_hrn)
952 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
953 creds.append(delegated_cred)
955 # options and call_id when supported
957 api_options ['call_id'] = unique_call_id()
958 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
959 # xxx no ReturnValue ??
962 def status(self, options, args):
964 retrieve slice status (SliverStatus)
966 server = self.sliceapi()
970 slice_urn = hrn_to_urn(slice_hrn, 'slice')
973 slice_cred = self.slice_credential_string(slice_hrn)
976 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
977 creds.append(delegated_cred)
979 # options and call_id when supported
981 api_options['call_id']=unique_call_id()
982 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
983 value = ReturnValue.get_value(result)
986 save_variable_to_file(value, options.file, options.fileformat)
988 def start(self, options, args):
990 start named slice (Start)
992 server = self.sliceapi()
996 slice_urn = hrn_to_urn(slice_hrn, 'slice')
999 slice_cred = self.slice_credential_string(args[0])
1000 creds = [slice_cred]
1001 if options.delegate:
1002 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1003 creds.append(delegated_cred)
1004 # xxx Thierry - does this not need an api_options as well ?
1005 return server.Start(slice_urn, creds)
1007 def stop(self, options, args):
1009 stop named slice (Stop)
1011 server = self.sliceapi()
1014 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1016 slice_cred = self.slice_credential_string(args[0])
1017 creds = [slice_cred]
1018 if options.delegate:
1019 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1020 creds.append(delegated_cred)
1021 return server.Stop(slice_urn, creds)
1024 def reset(self, options, args):
1026 reset named slice (reset_slice)
1028 server = self.sliceapi()
1031 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1033 slice_cred = self.slice_credential_string(args[0])
1034 creds = [slice_cred]
1035 if options.delegate:
1036 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1037 creds.append(delegated_cred)
1038 return server.reset_slice(creds, slice_urn)
1040 def renew(self, options, args):
1042 renew slice (RenewSliver)
1044 server = self.sliceapi()
1047 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1049 slice_cred = self.slice_credential_string(args[0])
1050 creds = [slice_cred]
1051 if options.delegate:
1052 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1053 creds.append(delegated_cred)
1056 # options and call_id when supported
1058 api_options['call_id']=unique_call_id()
1059 result = server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1060 value = ReturnValue.get_value(result)
1064 def shutdown(self, options, args):
1066 shutdown named slice (Shutdown)
1068 server = self.sliceapi()
1071 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1073 slice_cred = self.slice_credential_string(slice_hrn)
1074 creds = [slice_cred]
1075 if options.delegate:
1076 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1077 creds.append(delegated_cred)
1078 return server.Shutdown(slice_urn, creds)
1081 def get_ticket(self, options, args):
1083 get a ticket for the specified slice
1085 server = self.sliceapi()
1087 slice_hrn, rspec_path = args[0], args[1]
1088 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1090 slice_cred = self.slice_credential_string(slice_hrn)
1091 creds = [slice_cred]
1092 if options.delegate:
1093 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1094 creds.append(delegated_cred)
1096 rspec_file = self.get_rspec_file(rspec_path)
1097 rspec = open(rspec_file).read()
1098 # options and call_id when supported
1100 api_options['call_id']=unique_call_id()
1101 # get ticket at the server
1102 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1104 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1105 self.logger.info("writing ticket to %s"%file)
1106 ticket = SfaTicket(string=ticket_string)
1107 ticket.save_to_file(filename=file, save_parents=True)
1109 def redeem_ticket(self, options, args):
1111 Connects to nodes in a slice and redeems a ticket
1112 (slice hrn is retrieved from the ticket)
1114 ticket_file = args[0]
1116 # get slice hrn from the ticket
1117 # use this to get the right slice credential
1118 ticket = SfaTicket(filename=ticket_file)
1120 ticket_string = ticket.save_to_string(save_parents=True)
1122 slice_hrn = ticket.gidObject.get_hrn()
1123 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1124 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1125 slice_cred = self.slice_credential_string(slice_hrn)
1127 # get a list of node hostnames from the RSpec
1128 tree = etree.parse(StringIO(ticket.rspec))
1129 root = tree.getroot()
1130 hostnames = root.xpath("./network/site/node/hostname/text()")
1132 # create an xmlrpc connection to the component manager at each of these
1133 # components and gall redeem_ticket
1135 for hostname in hostnames:
1137 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1138 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1139 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1140 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1141 timeout=self.options.timeout, verbose=self.options.debug)
1142 server.RedeemTicket(ticket_string, slice_cred)
1143 self.logger.info("Success")
1144 except socket.gaierror:
1145 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1146 except Exception, e:
1147 self.logger.log_exc(e.message)
1150 def create_gid(self, options, args):
1152 Create a GID (CreateGid)
1157 target_hrn = args[0]
1158 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.bootstrap.my_gid_string())
1160 filename = options.file
1162 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1163 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1164 GID(string=gid).save_to_file(filename)
1167 def delegate(self, options, args):
1169 (locally) create delegate credential for use by given hrn
1171 delegee_hrn = args[0]
1172 if options.delegate_user:
1173 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1174 elif options.delegate_slice:
1175 slice_cred = self.slice_credential_string(options.delegate_slice)
1176 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1178 self.logger.warning("Must specify either --user or --slice <hrn>")
1180 delegated_cred = Credential(string=cred)
1181 object_hrn = delegated_cred.get_gid_object().get_hrn()
1182 if options.delegate_user:
1183 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1184 + get_leaf(object_hrn) + ".cred")
1185 elif options.delegate_slice:
1186 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1187 + get_leaf(object_hrn) + ".cred")
1189 delegated_cred.save_to_file(dest_fn, save_parents=True)
1191 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1193 def get_trusted_certs(self, options, args):
1195 return uhe trusted certs at this interface (get_trusted_certs)
1197 trusted_certs = self.registry().get_trusted_certs()
1198 for trusted_cert in trusted_certs:
1199 gid = GID(string=trusted_cert)
1201 cert = Certificate(string=trusted_cert)
1202 self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())