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
33 from sfa.rspecs.rspec import RSpec
34 from sfa.rspecs.rspec_converter import RSpecConverter
35 from sfa.rspecs.version_manager import VersionManager
37 from sfa.client.sfaclientlib import SfaClientBootstrap
38 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
39 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
40 from sfa.client.return_value import ReturnValue
44 # utility methods here
46 def display_rspec(rspec, format='rspec'):
48 tree = etree.parse(StringIO(rspec))
50 result = root.xpath("./network/site/node/hostname/text()")
51 elif format in ['ip']:
52 # The IP address is not yet part of the new RSpec
53 # so this doesn't do anything yet.
54 tree = etree.parse(StringIO(rspec))
56 result = root.xpath("./network/site/node/ipv4/text()")
63 def display_list(results):
64 for result in results:
67 def display_records(recordList, dump=False):
68 ''' Print all fields in the record'''
69 for record in recordList:
70 display_record(record, dump)
72 def display_record(record, dump=False):
76 info = record.getdict()
77 print "%s (%s)" % (info['hrn'], info['type'])
81 def filter_records(type, records):
83 for record in records:
84 if (record['type'] == type) or (type == "all"):
85 filtered_records.append(record)
86 return filtered_records
90 def save_variable_to_file(var, filename, format="text"):
91 f = open(filename, "w")
94 elif format == "pickled":
95 f.write(pickle.dumps(var))
97 # this should never happen
98 print "unknown output format", format
101 def save_rspec_to_file(rspec, filename):
102 if not filename.endswith(".rspec"):
103 filename = filename + ".rspec"
104 f = open(filename, 'w')
109 def save_records_to_file(filename, recordList, format="xml"):
112 for record in recordList:
114 save_record_to_file(filename + "." + str(index), record)
116 save_record_to_file(filename, record)
118 elif format == "xmllist":
119 f = open(filename, "w")
120 f.write("<recordlist>\n")
121 for record in recordList:
122 record = SfaRecord(dict=record)
123 f.write('<record hrn="' + record.get_name() + '" type="' + record.get_type() + '" />\n')
124 f.write("</recordlist>\n")
126 elif format == "hrnlist":
127 f = open(filename, "w")
128 for record in recordList:
129 record = SfaRecord(dict=record)
130 f.write(record.get_name() + "\n")
133 # this should never happen
134 print "unknown output format", format
136 def save_record_to_file(filename, record):
137 if record['type'] in ['user']:
138 record = UserRecord(dict=record)
139 elif record['type'] in ['slice']:
140 record = SliceRecord(dict=record)
141 elif record['type'] in ['node']:
142 record = NodeRecord(dict=record)
143 elif record['type'] in ['authority', 'ma', 'sa']:
144 record = AuthorityRecord(dict=record)
146 record = SfaRecord(dict=record)
147 str = record.save_to_string()
148 f=codecs.open(filename, encoding='utf-8',mode="w")
155 def load_record_from_file(filename):
156 f=codecs.open(filename, encoding="utf-8", mode="r")
159 record = SfaRecord(string=str)
164 def unique_call_id(): return uuid.uuid4().urn
168 # dirty hack to make this class usable from the outside
169 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
172 def default_sfi_dir ():
173 if os.path.isfile("./sfi_config"):
176 return os.path.expanduser("~/.sfi/")
178 # dummy to meet Sfi's expectations for its 'options' field
179 # i.e. s/t we can do setattr on
183 def __init__ (self,options=None):
184 if options is None: options=Sfi.DummyOptions()
185 for opt in Sfi.required_options:
186 if not hasattr(options,opt): setattr(options,opt,None)
187 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
188 self.options = options
190 self.authority = None
191 self.logger = sfi_logger
192 self.logger.enable_console()
193 self.available_names = [ tuple[0] for tuple in Sfi.available ]
194 self.available_dict = dict (Sfi.available)
196 # tuples command-name expected-args in the order in which they should appear in the help
199 ("list", "authority"),
202 ("update", "record"),
205 ("resources", "[slice_hrn]"),
206 ("create", "slice_hrn rspec"),
207 ("delete", "slice_hrn"),
208 ("status", "slice_hrn"),
209 ("start", "slice_hrn"),
210 ("stop", "slice_hrn"),
211 ("reset", "slice_hrn"),
212 ("renew", "slice_hrn time"),
213 ("shutdown", "slice_hrn"),
214 ("get_ticket", "slice_hrn rspec"),
215 ("redeem_ticket", "ticket"),
216 ("delegate", "name"),
217 ("create_gid", "[name]"),
218 ("get_trusted_certs", "cred"),
221 def print_command_help (self, options):
222 verbose=getattr(options,'verbose')
223 format3="%18s %-15s %s"
226 print format3%("command","cmd_args","description")
230 self.create_parser().print_help()
231 for command in self.available_names:
232 args=self.available_dict[command]
233 method=getattr(self,command,None)
235 if method: doc=getattr(method,'__doc__',"")
236 if not doc: doc="*** no doc found ***"
237 doc=doc.strip(" \t\n")
238 doc=doc.replace("\n","\n"+35*' ')
241 print format3%(command,args,doc)
243 self.create_command_parser(command).print_help()
245 def create_command_parser(self, command):
246 if command not in self.available_dict:
247 msg="Invalid command\n"
249 msg += ','.join(self.available_names)
250 self.logger.critical(msg)
253 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
254 % (command, self.available_dict[command]))
256 # user specifies remote aggregate/sm/component
257 if command in ("resources", "slices", "create", "delete", "start", "stop",
258 "restart", "shutdown", "get_ticket", "renew", "status"):
259 parser.add_option("-d", "--delegate", dest="delegate", default=None,
261 help="Include a credential delegated to the user's root"+\
262 "authority in set of credentials for this call")
264 # registy filter option
265 if command in ("list", "show", "remove"):
266 parser.add_option("-t", "--type", dest="type", type="choice",
267 help="type filter ([all]|user|slice|authority|node|aggregate)",
268 choices=("all", "user", "slice", "authority", "node", "aggregate"),
270 if command in ("resources"):
272 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
273 help="schema type and version of resulting RSpec")
274 # disable/enable cached rspecs
275 parser.add_option("-c", "--current", dest="current", default=False,
277 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
279 parser.add_option("-f", "--format", dest="format", type="choice",
280 help="display format ([xml]|dns|ip)", default="xml",
281 choices=("xml", "dns", "ip"))
282 #panos: a new option to define the type of information about resources a user is interested in
283 parser.add_option("-i", "--info", dest="info",
284 help="optional component information", default=None)
287 # 'create' does return the new rspec, makes sense to save that too
288 if command in ("resources", "show", "list", "create_gid", 'create'):
289 parser.add_option("-o", "--output", dest="file",
290 help="output XML to file", metavar="FILE", default=None)
292 if command in ("show", "list"):
293 parser.add_option("-f", "--format", dest="format", type="choice",
294 help="display format ([text]|xml)", default="text",
295 choices=("text", "xml"))
297 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
298 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
299 choices=("xml", "xmllist", "hrnlist"))
301 if command in ("status", "version"):
302 parser.add_option("-o", "--output", dest="file",
303 help="output dictionary to file", metavar="FILE", default=None)
304 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
305 help="output file format ([text]|pickled)", default="text",
306 choices=("text","pickled"))
308 if command in ("delegate"):
309 parser.add_option("-u", "--user",
310 action="store_true", dest="delegate_user", default=False,
311 help="delegate user credential")
312 parser.add_option("-s", "--slice", dest="delegate_slice",
313 help="delegate slice credential", metavar="HRN", default=None)
315 if command in ("version"):
316 parser.add_option("-R","--registry-version",
317 action="store_true", dest="version_registry", default=False,
318 help="probe registry version instead of sliceapi")
319 parser.add_option("-l","--local",
320 action="store_true", dest="version_local", default=False,
321 help="display version of the local client")
326 def create_parser(self):
328 # Generate command line parser
329 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
330 description="Commands: %s"%(" ".join(self.available_names)))
331 parser.add_option("-r", "--registry", dest="registry",
332 help="root registry", metavar="URL", default=None)
333 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
334 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
335 parser.add_option("-d", "--dir", dest="sfi_dir",
336 help="config & working directory - default is %default",
337 metavar="PATH", default=Sfi.default_sfi_dir())
338 parser.add_option("-u", "--user", dest="user",
339 help="user name", metavar="HRN", default=None)
340 parser.add_option("-a", "--auth", dest="auth",
341 help="authority name", metavar="HRN", default=None)
342 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
343 help="verbose mode - cumulative")
344 parser.add_option("-D", "--debug",
345 action="store_true", dest="debug", default=False,
346 help="Debug (xml-rpc) protocol messages")
347 # would it make sense to use ~/.ssh/id_rsa as a default here ?
348 parser.add_option("-k", "--private-key",
349 action="store", dest="user_private_key", default=None,
350 help="point to the private key file to use if not yet installed in sfi_dir")
351 parser.add_option("-t", "--timeout", dest="timeout", default=None,
352 help="Amout of time to wait before timing out the request")
353 parser.add_option("-?", "--commands",
354 action="store_true", dest="command_help", default=False,
355 help="one page summary on commands & exit")
356 parser.disable_interspersed_args()
361 def print_help (self):
362 self.sfi_parser.print_help()
363 self.command_parser.print_help()
366 # Main: parse arguments and dispatch to command
368 def dispatch(self, command, command_options, command_args):
369 return getattr(self, command)(command_options, command_args)
372 self.sfi_parser = self.create_parser()
373 (options, args) = self.sfi_parser.parse_args()
374 if options.command_help:
375 self.print_command_help(options)
377 self.options = options
379 self.logger.setLevelFromOptVerbose(self.options.verbose)
382 self.logger.critical("No command given. Use -h for help.")
383 self.print_command_help(options)
387 self.command_parser = self.create_command_parser(command)
388 (command_options, command_args) = self.command_parser.parse_args(args[1:])
389 self.command_options = command_options
393 self.logger.info("Command=%s" % command)
396 self.dispatch(command, command_options, command_args)
398 self.logger.critical ("Unknown command %s"%command)
405 def read_config(self):
406 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
408 config = Config (config_file)
410 self.logger.critical("Failed to read configuration file %s"%config_file)
411 self.logger.info("Make sure to remove the export clauses and to add quotes")
412 if self.options.verbose==0:
413 self.logger.info("Re-run with -v for more details")
415 self.logger.log_exc("Could not read config file %s"%config_file)
420 if (self.options.sm is not None):
421 self.sm_url = self.options.sm
422 elif hasattr(config, "SFI_SM"):
423 self.sm_url = config.SFI_SM
425 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
429 if (self.options.registry is not None):
430 self.reg_url = self.options.registry
431 elif hasattr(config, "SFI_REGISTRY"):
432 self.reg_url = config.SFI_REGISTRY
434 self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
438 if (self.options.user is not None):
439 self.user = self.options.user
440 elif hasattr(config, "SFI_USER"):
441 self.user = config.SFI_USER
443 self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
447 if (self.options.auth is not None):
448 self.authority = self.options.auth
449 elif hasattr(config, "SFI_AUTH"):
450 self.authority = config.SFI_AUTH
452 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
459 # Get various credential and spec files
461 # Establishes limiting conventions
462 # - conflates MAs and SAs
463 # - assumes last token in slice name is unique
465 # Bootstraps credentials
466 # - bootstrap user credential from self-signed certificate
467 # - bootstrap authority credential from user credential
468 # - bootstrap slice credential from user credential
471 # init self-signed cert, user credentials and gid
472 def bootstrap (self):
473 bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
474 # if -k is provided, use this to initialize private key
475 if self.options.user_private_key:
476 bootstrap.init_private_key_if_missing (self.options.user_private_key)
478 # trigger legacy compat code if needed
479 # the name has changed from just <leaf>.pkey to <hrn>.pkey
480 if not os.path.isfile(bootstrap.private_key_filename()):
481 self.logger.info ("private key not found, trying legacy name")
483 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
484 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
485 bootstrap.init_private_key_if_missing (legacy_private_key)
486 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
488 self.logger.log_exc("Can't find private key ")
492 bootstrap.bootstrap_my_gid()
493 # extract what's needed
494 self.private_key = bootstrap.private_key()
495 self.my_credential_string = bootstrap.my_credential_string ()
496 self.my_gid = bootstrap.my_gid ()
497 self.bootstrap = bootstrap
500 def my_authority_credential_string(self):
501 if not self.authority:
502 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
504 return self.bootstrap.authority_credential_string (self.authority)
506 def slice_credential_string(self, name):
507 return self.bootstrap.slice_credential_string (name)
509 # xxx should be supported by sfaclientbootstrap as well
510 def delegate_cred(self, object_cred, hrn, type='authority'):
511 # the gid and hrn of the object we are delegating
512 if isinstance(object_cred, str):
513 object_cred = Credential(string=object_cred)
514 object_gid = object_cred.get_gid_object()
515 object_hrn = object_gid.get_hrn()
517 if not object_cred.get_privileges().get_all_delegate():
518 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
521 # the delegating user's gid
522 caller_gidfile = self.my_gid()
524 # the gid of the user who will be delegated to
525 delegee_gid = self.bootstrap.gid(hrn,type)
526 delegee_hrn = delegee_gid.get_hrn()
527 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
528 return dcred.save_to_string(save_parents=True)
531 # Management of the servers
536 if not hasattr (self, 'registry_proxy'):
537 self.logger.info("Contacting Registry at: %s"%self.reg_url)
538 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
539 timeout=self.options.timeout, verbose=self.options.debug)
540 return self.registry_proxy
544 if not hasattr (self, 'sliceapi_proxy'):
545 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
546 if hasattr(self.command_options,'component') and self.command_options.component:
547 # resolve the hrn at the registry
548 node_hrn = self.command_options.component
549 records = self.registry().Resolve(node_hrn, self.my_credential_string)
550 records = filter_records('node', records)
552 self.logger.warning("No such component:%r"% opts.component)
554 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
555 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
557 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
558 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
559 self.sm_url = 'http://' + self.sm_url
560 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
561 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
562 timeout=self.options.timeout, verbose=self.options.debug)
563 return self.sliceapi_proxy
565 def get_cached_server_version(self, server):
566 # check local cache first
569 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
570 cache_key = server.url + "-version"
572 cache = Cache(cache_file)
575 self.logger.info("Local cache not found at: %s" % cache_file)
578 version = cache.get(cache_key)
581 result = server.GetVersion()
582 version= ReturnValue.get_value(result)
583 # cache version for 20 minutes
584 cache.add(cache_key, version, ttl= 60*20)
585 self.logger.info("Updating cache file %s" % cache_file)
586 cache.save_to_file(cache_file)
590 ### resurrect this temporarily so we can support V1 aggregates for a while
591 def server_supports_options_arg(self, server):
593 Returns true if server support the optional call_id arg, false otherwise.
595 server_version = self.get_cached_server_version(server)
597 # xxx need to rewrite this
598 if int(server_version.get('geni_api')) >= 2:
602 def server_supports_call_id_arg(self, server):
603 server_version = self.get_cached_server_version(server)
605 if 'sfa' in server_version and 'code_tag' in server_version:
606 code_tag = server_version['code_tag']
607 code_tag_parts = code_tag.split("-")
608 version_parts = code_tag_parts[0].split(".")
609 major, minor = version_parts[0], version_parts[1]
610 rev = code_tag_parts[1]
611 if int(major) == 1 and minor == 0 and build >= 22:
615 ### ois = options if supported
616 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
617 def ois (self, server, option_dict):
618 if self.server_supports_options_arg (server):
620 elif self.server_supports_call_id_arg (server):
621 return [ unique_call_id () ]
625 ### cis = call_id if supported - like ois
626 def cis (self, server):
627 if self.server_supports_call_id_arg (server):
628 return [ unique_call_id ]
632 ######################################## miscell utilities
633 def get_rspec_file(self, rspec):
634 if (os.path.isabs(rspec)):
637 file = os.path.join(self.options.sfi_dir, rspec)
638 if (os.path.isfile(file)):
641 self.logger.critical("No such rspec file %s"%rspec)
644 def get_record_file(self, record):
645 if (os.path.isabs(record)):
648 file = os.path.join(self.options.sfi_dir, record)
649 if (os.path.isfile(file)):
652 self.logger.critical("No such registry record file %s"%record)
656 #==========================================================================
657 # Following functions implement the commands
659 # Registry-related commands
660 #==========================================================================
662 def version(self, options, args):
664 display an SFA server version (GetVersion)
665 or version information about sfi itself
667 if options.version_local:
668 version=version_core()
670 if options.version_registry:
671 server=self.registry()
673 server = self.sliceapi()
674 result = server.GetVersion()
675 version = ReturnValue.get_value(result)
676 pprinter = PrettyPrinter(indent=4)
677 pprinter.pprint(version)
679 save_variable_to_file(version, options.file, options.fileformat)
681 def list(self, options, args):
683 list entries in named authority registry (List)
690 list = self.registry().List(hrn, self.my_credential_string)
692 raise Exception, "Not enough parameters for the 'list' command"
694 # filter on person, slice, site, node, etc.
695 # THis really should be in the self.filter_records funct def comment...
696 list = filter_records(options.type, list)
698 print "%s (%s)" % (record['hrn'], record['type'])
700 save_records_to_file(options.file, list, options.fileformat)
703 def show(self, options, args):
705 show details about named registry record (Resolve)
711 records = self.registry().Resolve(hrn, self.my_credential_string)
712 records = filter_records(options.type, records)
714 self.logger.error("No record of type %s"% options.type)
715 for record in records:
716 if record['type'] in ['user']:
717 record = UserRecord(dict=record)
718 elif record['type'] in ['slice']:
719 record = SliceRecord(dict=record)
720 elif record['type'] in ['node']:
721 record = NodeRecord(dict=record)
722 elif record['type'].startswith('authority'):
723 record = AuthorityRecord(dict=record)
725 record = SfaRecord(dict=record)
726 if (options.format == "text"):
729 print record.save_to_string()
731 save_records_to_file(options.file, records, options.fileformat)
734 def add(self, options, args):
735 "add record into registry from xml file (Register)"
736 auth_cred = self.my_authority_credential_string()
740 record_filepath = args[0]
741 rec_file = self.get_record_file(record_filepath)
742 record = load_record_from_file(rec_file).as_dict()
743 return self.registry().Register(record, auth_cred)
745 def update(self, options, args):
746 "update record into registry from xml file (Update)"
750 rec_file = self.get_record_file(args[0])
751 record = load_record_from_file(rec_file)
752 if record['type'] == "user":
753 if record.get_name() == self.user:
754 cred = self.my_credential_string
756 cred = self.my_authority_credential_string()
757 elif record['type'] in ["slice"]:
759 cred = self.slice_credential_string(record.get_name())
760 except ServerException, e:
761 # XXX smbaker -- once we have better error return codes, update this
762 # to do something better than a string compare
763 if "Permission error" in e.args[0]:
764 cred = self.my_authority_credential_string()
767 elif record.get_type() in ["authority"]:
768 cred = self.my_authority_credential_string()
769 elif record.get_type() == 'node':
770 cred = self.my_authority_credential_string()
772 raise "unknown record type" + record.get_type()
773 record = record.as_dict()
774 return self.registry().Update(record, cred)
776 def remove(self, options, args):
777 "remove registry record by name (Remove)"
778 auth_cred = self.my_authority_credential_string()
786 return self.registry().Remove(hrn, auth_cred, type)
788 # ==================================================================
789 # Slice-related commands
790 # ==================================================================
792 def slices(self, options, args):
793 "list instantiated slices (ListSlices) - returns urn's"
794 server = self.sliceapi()
796 creds = [self.my_credential_string]
798 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
799 creds.append(delegated_cred)
800 # options and call_id when supported
802 api_options['call_id']=unique_call_id()
803 result = server.ListSlices(creds, *self.ois(server,api_options))
804 value = ReturnValue.get_value(result)
808 # show rspec for named slice
809 def resources(self, options, args):
811 with no arg, discover available resources, (ListResources)
812 or with an slice hrn, shows currently provisioned resources
814 server = self.sliceapi()
819 creds.append(self.slice_credential_string(args[0]))
821 creds.append(self.my_credential_string)
823 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
825 # no need to check if server accepts the options argument since the options has
826 # been a required argument since v1 API
828 # always send call_id to v2 servers
829 api_options ['call_id'] = unique_call_id()
830 # ask for cached value if available
831 api_options ['cached'] = True
834 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
836 api_options['info'] = options.info
838 if options.current == True:
839 api_options['cached'] = False
841 api_options['cached'] = True
842 if options.rspec_version:
843 version_manager = VersionManager()
844 server_version = self.get_cached_server_version(server)
845 if 'sfa' in server_version:
846 # just request the version the client wants
847 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
849 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
851 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
852 result = server.ListResources (creds, api_options)
853 value = ReturnValue.get_value(result)
854 if options.file is None:
855 display_rspec(value, options.format)
857 save_rspec_to_file(value, options.file)
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 print >>sys.stderr, "\r\n \r\n \r\n WOOOOOO"
909 users = sfa_users_arg(user_records, slice_record)
911 # do not append users, keys, or slice tags. Anything
912 # not contained in this request will be removed from the slice
914 # CreateSliver has supported the options argument for a while now so it should
915 # be safe to assume this server support it
917 api_options ['append'] = False
918 api_options ['call_id'] = unique_call_id()
920 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
921 value = ReturnValue.get_value(result)
922 if options.file is None:
925 save_rspec_to_file (value, options.file)
928 def delete(self, options, args):
930 delete named slice (DeleteSliver)
932 server = self.sliceapi()
936 slice_urn = hrn_to_urn(slice_hrn, 'slice')
939 slice_cred = self.slice_credential_string(slice_hrn)
942 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
943 creds.append(delegated_cred)
945 # options and call_id when supported
947 api_options ['call_id'] = unique_call_id()
948 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
949 # xxx no ReturnValue ??
952 def status(self, options, args):
954 retrieve slice status (SliverStatus)
956 server = self.sliceapi()
960 slice_urn = hrn_to_urn(slice_hrn, 'slice')
963 slice_cred = self.slice_credential_string(slice_hrn)
966 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
967 creds.append(delegated_cred)
969 # options and call_id when supported
971 api_options['call_id']=unique_call_id()
972 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
973 value = ReturnValue.get_value(result)
976 save_variable_to_file(value, options.file, options.fileformat)
978 def start(self, options, args):
980 start named slice (Start)
982 server = self.sliceapi()
986 slice_urn = hrn_to_urn(slice_hrn, 'slice')
989 slice_cred = self.slice_credential_string(args[0])
992 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
993 creds.append(delegated_cred)
994 # xxx Thierry - does this not need an api_options as well ?
995 return server.Start(slice_urn, creds)
997 def stop(self, options, args):
999 stop named slice (Stop)
1001 server = self.sliceapi()
1004 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1006 slice_cred = self.slice_credential_string(args[0])
1007 creds = [slice_cred]
1008 if options.delegate:
1009 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1010 creds.append(delegated_cred)
1011 return server.Stop(slice_urn, creds)
1014 def reset(self, options, args):
1016 reset named slice (reset_slice)
1018 server = self.sliceapi()
1021 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1023 slice_cred = self.slice_credential_string(args[0])
1024 creds = [slice_cred]
1025 if options.delegate:
1026 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1027 creds.append(delegated_cred)
1028 return server.reset_slice(creds, slice_urn)
1030 def renew(self, options, args):
1032 renew slice (RenewSliver)
1034 server = self.sliceapi()
1037 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1039 slice_cred = self.slice_credential_string(args[0])
1040 creds = [slice_cred]
1041 if options.delegate:
1042 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1043 creds.append(delegated_cred)
1046 # options and call_id when supported
1048 api_options['call_id']=unique_call_id()
1049 result = server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1050 value = ReturnValue.get_value(result)
1054 def shutdown(self, options, args):
1056 shutdown named slice (Shutdown)
1058 server = self.sliceapi()
1061 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1063 slice_cred = self.slice_credential_string(slice_hrn)
1064 creds = [slice_cred]
1065 if options.delegate:
1066 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1067 creds.append(delegated_cred)
1068 return server.Shutdown(slice_urn, creds)
1071 def get_ticket(self, options, args):
1073 get a ticket for the specified slice
1075 server = self.sliceapi()
1077 slice_hrn, rspec_path = args[0], args[1]
1078 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1080 slice_cred = self.slice_credential_string(slice_hrn)
1081 creds = [slice_cred]
1082 if options.delegate:
1083 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1084 creds.append(delegated_cred)
1086 rspec_file = self.get_rspec_file(rspec_path)
1087 rspec = open(rspec_file).read()
1088 # options and call_id when supported
1090 api_options['call_id']=unique_call_id()
1091 # get ticket at the server
1092 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1094 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1095 self.logger.info("writing ticket to %s"%file)
1096 ticket = SfaTicket(string=ticket_string)
1097 ticket.save_to_file(filename=file, save_parents=True)
1099 def redeem_ticket(self, options, args):
1101 Connects to nodes in a slice and redeems a ticket
1102 (slice hrn is retrieved from the ticket)
1104 ticket_file = args[0]
1106 # get slice hrn from the ticket
1107 # use this to get the right slice credential
1108 ticket = SfaTicket(filename=ticket_file)
1110 ticket_string = ticket.save_to_string(save_parents=True)
1112 slice_hrn = ticket.gidObject.get_hrn()
1113 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1114 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1115 slice_cred = self.slice_credential_string(slice_hrn)
1117 # get a list of node hostnames from the RSpec
1118 tree = etree.parse(StringIO(ticket.rspec))
1119 root = tree.getroot()
1120 hostnames = root.xpath("./network/site/node/hostname/text()")
1122 # create an xmlrpc connection to the component manager at each of these
1123 # components and gall redeem_ticket
1125 for hostname in hostnames:
1127 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1128 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1129 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1130 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1131 timeout=self.options.timeout, verbose=self.options.debug)
1132 server.RedeemTicket(ticket_string, slice_cred)
1133 self.logger.info("Success")
1134 except socket.gaierror:
1135 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1136 except Exception, e:
1137 self.logger.log_exc(e.message)
1140 def create_gid(self, options, args):
1142 Create a GID (CreateGid)
1147 target_hrn = args[0]
1148 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.bootstrap.my_gid_string())
1150 filename = options.file
1152 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1153 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1154 GID(string=gid).save_to_file(filename)
1157 def delegate(self, options, args):
1159 (locally) create delegate credential for use by given hrn
1161 delegee_hrn = args[0]
1162 if options.delegate_user:
1163 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1164 elif options.delegate_slice:
1165 slice_cred = self.slice_credential_string(options.delegate_slice)
1166 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1168 self.logger.warning("Must specify either --user or --slice <hrn>")
1170 delegated_cred = Credential(string=cred)
1171 object_hrn = delegated_cred.get_gid_object().get_hrn()
1172 if options.delegate_user:
1173 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1174 + get_leaf(object_hrn) + ".cred")
1175 elif options.delegate_slice:
1176 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1177 + get_leaf(object_hrn) + ".cred")
1179 delegated_cred.save_to_file(dest_fn, save_parents=True)
1181 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1183 def get_trusted_certs(self, options, args):
1185 return uhe trusted certs at this interface (get_trusted_certs)
1187 trusted_certs = self.registry().get_trusted_certs()
1188 for trusted_cert in trusted_certs:
1189 gid = GID(string=trusted_cert)
1191 cert = Certificate(string=trusted_cert)
1192 self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())