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.record import Record
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_raw_to_file(var, filename, format="text", banner=None):
93 # if filename is "-", send it to stdout
96 f = open(filename, "w")
101 elif format == "pickled":
102 f.write(pickle.dumps(var))
103 elif format == "json":
104 if hasattr(json, "dumps"):
105 f.write(json.dumps(var)) # python 2.6
107 f.write(json.write(var)) # python 2.5
109 # this should never happen
110 print "unknown output format", format
112 f.write('\n'+banner+"\n")
114 def save_rspec_to_file(rspec, filename):
115 if not filename.endswith(".rspec"):
116 filename = filename + ".rspec"
117 f = open(filename, 'w')
122 def save_records_to_file(filename, record_dicts, format="xml"):
125 for record_dict in record_dicts:
127 save_record_to_file(filename + "." + str(index), record_dict)
129 save_record_to_file(filename, record_dict)
131 elif format == "xmllist":
132 f = open(filename, "w")
133 f.write("<recordlist>\n")
134 for record_dict in record_dicts:
135 record_obj=Record(dict=record_dict)
136 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
137 f.write("</recordlist>\n")
139 elif format == "hrnlist":
140 f = open(filename, "w")
141 for record_dict in record_dicts:
142 record_obj=Record(dict=record_dict)
143 f.write(record_obj.hrn + "\n")
146 # this should never happen
147 print "unknown output format", format
149 def save_record_to_file(filename, record_dict):
150 record = Record(dict=record_dict)
151 xml = record.save_as_xml()
152 f=codecs.open(filename, encoding='utf-8',mode="w")
159 def load_record_from_file(filename):
160 f=codecs.open(filename, encoding="utf-8", mode="r")
161 xml_string = f.read()
163 return Record(xml=xml_string)
167 def unique_call_id(): return uuid.uuid4().urn
171 # dirty hack to make this class usable from the outside
172 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
175 def default_sfi_dir ():
176 if os.path.isfile("./sfi_config"):
179 return os.path.expanduser("~/.sfi/")
181 # dummy to meet Sfi's expectations for its 'options' field
182 # i.e. s/t we can do setattr on
186 def __init__ (self,options=None):
187 if options is None: options=Sfi.DummyOptions()
188 for opt in Sfi.required_options:
189 if not hasattr(options,opt): setattr(options,opt,None)
190 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
191 self.options = options
193 self.authority = None
194 self.logger = sfi_logger
195 self.logger.enable_console()
196 self.available_names = [ tuple[0] for tuple in Sfi.available ]
197 self.available_dict = dict (Sfi.available)
199 # tuples command-name expected-args in the order in which they should appear in the help
202 ("list", "authority"),
205 ("update", "record"),
208 ("resources", "[slice_hrn]"),
209 ("create", "slice_hrn rspec"),
210 ("delete", "slice_hrn"),
211 ("status", "slice_hrn"),
212 ("start", "slice_hrn"),
213 ("stop", "slice_hrn"),
214 ("reset", "slice_hrn"),
215 ("renew", "slice_hrn time"),
216 ("shutdown", "slice_hrn"),
217 ("get_ticket", "slice_hrn rspec"),
218 ("redeem_ticket", "ticket"),
219 ("delegate", "name"),
220 ("create_gid", "[name]"),
221 ("get_trusted_certs", "cred"),
224 def print_command_help (self, options):
225 verbose=getattr(options,'verbose')
226 format3="%18s %-15s %s"
229 print format3%("command","cmd_args","description")
233 self.create_parser().print_help()
234 for command in self.available_names:
235 args=self.available_dict[command]
236 method=getattr(self,command,None)
238 if method: doc=getattr(method,'__doc__',"")
239 if not doc: doc="*** no doc found ***"
240 doc=doc.strip(" \t\n")
241 doc=doc.replace("\n","\n"+35*' ')
244 print format3%(command,args,doc)
246 self.create_command_parser(command).print_help()
248 def create_command_parser(self, command):
249 if command not in self.available_dict:
250 msg="Invalid command\n"
252 msg += ','.join(self.available_names)
253 self.logger.critical(msg)
256 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
257 % (command, self.available_dict[command]))
259 # user specifies remote aggregate/sm/component
260 if command in ("resources", "slices", "create", "delete", "start", "stop",
261 "restart", "shutdown", "get_ticket", "renew", "status"):
262 parser.add_option("-d", "--delegate", dest="delegate", default=None,
264 help="Include a credential delegated to the user's root"+\
265 "authority in set of credentials for this call")
267 # registy filter option
268 if command in ("list", "show", "remove"):
269 parser.add_option("-t", "--type", dest="type", type="choice",
270 help="type filter ([all]|user|slice|authority|node|aggregate)",
271 choices=("all", "user", "slice", "authority", "node", "aggregate"),
273 if command in ("resources"):
275 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
276 help="schema type and version of resulting RSpec")
277 # disable/enable cached rspecs
278 parser.add_option("-c", "--current", dest="current", default=False,
280 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
282 parser.add_option("-f", "--format", dest="format", type="choice",
283 help="display format ([xml]|dns|ip)", default="xml",
284 choices=("xml", "dns", "ip"))
285 #panos: a new option to define the type of information about resources a user is interested in
286 parser.add_option("-i", "--info", dest="info",
287 help="optional component information", default=None)
290 # 'create' does return the new rspec, makes sense to save that too
291 if command in ("resources", "show", "list", "create_gid", 'create'):
292 parser.add_option("-o", "--output", dest="file",
293 help="output XML to file", metavar="FILE", default=None)
295 if command in ("show", "list"):
296 parser.add_option("-f", "--format", dest="format", type="choice",
297 help="display format ([text]|xml)", default="text",
298 choices=("text", "xml"))
300 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
301 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
302 choices=("xml", "xmllist", "hrnlist"))
304 if command in ("delegate"):
305 parser.add_option("-u", "--user",
306 action="store_true", dest="delegate_user", default=False,
307 help="delegate user credential")
308 parser.add_option("-s", "--slice", dest="delegate_slice",
309 help="delegate slice credential", metavar="HRN", default=None)
311 if command in ("version"):
312 parser.add_option("-R","--registry-version",
313 action="store_true", dest="version_registry", default=False,
314 help="probe registry version instead of sliceapi")
315 parser.add_option("-l","--local",
316 action="store_true", dest="version_local", default=False,
317 help="display version of the local client")
322 def create_parser(self):
324 # Generate command line parser
325 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
326 description="Commands: %s"%(" ".join(self.available_names)))
327 parser.add_option("-r", "--registry", dest="registry",
328 help="root registry", metavar="URL", default=None)
329 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
330 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
331 parser.add_option("-R", "--raw", dest="raw", default=None,
332 help="Save raw, unparsed server response to a file")
333 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
334 help="raw file format ([text]|pickled|json)", default="text",
335 choices=("text","pickled","json"))
336 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
337 help="text string to write before and after raw output")
338 parser.add_option("-d", "--dir", dest="sfi_dir",
339 help="config & working directory - default is %default",
340 metavar="PATH", default=Sfi.default_sfi_dir())
341 parser.add_option("-u", "--user", dest="user",
342 help="user name", metavar="HRN", default=None)
343 parser.add_option("-a", "--auth", dest="auth",
344 help="authority name", metavar="HRN", default=None)
345 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
346 help="verbose mode - cumulative")
347 parser.add_option("-D", "--debug",
348 action="store_true", dest="debug", default=False,
349 help="Debug (xml-rpc) protocol messages")
350 # would it make sense to use ~/.ssh/id_rsa as a default here ?
351 parser.add_option("-k", "--private-key",
352 action="store", dest="user_private_key", default=None,
353 help="point to the private key file to use if not yet installed in sfi_dir")
354 parser.add_option("-t", "--timeout", dest="timeout", default=None,
355 help="Amout of time to wait before timing out the request")
356 parser.add_option("-?", "--commands",
357 action="store_true", dest="command_help", default=False,
358 help="one page summary on commands & exit")
359 parser.disable_interspersed_args()
364 def print_help (self):
365 self.sfi_parser.print_help()
366 self.command_parser.print_help()
369 # Main: parse arguments and dispatch to command
371 def dispatch(self, command, command_options, command_args):
372 return getattr(self, command)(command_options, command_args)
375 self.sfi_parser = self.create_parser()
376 (options, args) = self.sfi_parser.parse_args()
377 if options.command_help:
378 self.print_command_help(options)
380 self.options = options
382 self.logger.setLevelFromOptVerbose(self.options.verbose)
385 self.logger.critical("No command given. Use -h for help.")
386 self.print_command_help(options)
390 self.command_parser = self.create_command_parser(command)
391 (command_options, command_args) = self.command_parser.parse_args(args[1:])
392 self.command_options = command_options
396 self.logger.info("Command=%s" % command)
399 self.dispatch(command, command_options, command_args)
401 self.logger.critical ("Unknown command %s"%command)
408 def read_config(self):
409 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
411 config = Config (config_file)
413 self.logger.critical("Failed to read configuration file %s"%config_file)
414 self.logger.info("Make sure to remove the export clauses and to add quotes")
415 if self.options.verbose==0:
416 self.logger.info("Re-run with -v for more details")
418 self.logger.log_exc("Could not read config file %s"%config_file)
423 if (self.options.sm is not None):
424 self.sm_url = self.options.sm
425 elif hasattr(config, "SFI_SM"):
426 self.sm_url = config.SFI_SM
428 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
432 if (self.options.registry is not None):
433 self.reg_url = self.options.registry
434 elif hasattr(config, "SFI_REGISTRY"):
435 self.reg_url = config.SFI_REGISTRY
437 self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
441 if (self.options.user is not None):
442 self.user = self.options.user
443 elif hasattr(config, "SFI_USER"):
444 self.user = config.SFI_USER
446 self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
450 if (self.options.auth is not None):
451 self.authority = self.options.auth
452 elif hasattr(config, "SFI_AUTH"):
453 self.authority = config.SFI_AUTH
455 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
462 # Get various credential and spec files
464 # Establishes limiting conventions
465 # - conflates MAs and SAs
466 # - assumes last token in slice name is unique
468 # Bootstraps credentials
469 # - bootstrap user credential from self-signed certificate
470 # - bootstrap authority credential from user credential
471 # - bootstrap slice credential from user credential
474 # init self-signed cert, user credentials and gid
475 def bootstrap (self):
476 bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
477 # if -k is provided, use this to initialize private key
478 if self.options.user_private_key:
479 bootstrap.init_private_key_if_missing (self.options.user_private_key)
481 # trigger legacy compat code if needed
482 # the name has changed from just <leaf>.pkey to <hrn>.pkey
483 if not os.path.isfile(bootstrap.private_key_filename()):
484 self.logger.info ("private key not found, trying legacy name")
486 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
487 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
488 bootstrap.init_private_key_if_missing (legacy_private_key)
489 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
491 self.logger.log_exc("Can't find private key ")
495 bootstrap.bootstrap_my_gid()
496 # extract what's needed
497 self.private_key = bootstrap.private_key()
498 self.my_credential_string = bootstrap.my_credential_string ()
499 self.my_gid = bootstrap.my_gid ()
500 self.bootstrap = bootstrap
503 def my_authority_credential_string(self):
504 if not self.authority:
505 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
507 return self.bootstrap.authority_credential_string (self.authority)
509 def slice_credential_string(self, name):
510 return self.bootstrap.slice_credential_string (name)
512 # xxx should be supported by sfaclientbootstrap as well
513 def delegate_cred(self, object_cred, hrn, type='authority'):
514 # the gid and hrn of the object we are delegating
515 if isinstance(object_cred, str):
516 object_cred = Credential(string=object_cred)
517 object_gid = object_cred.get_gid_object()
518 object_hrn = object_gid.get_hrn()
520 if not object_cred.get_privileges().get_all_delegate():
521 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
524 # the delegating user's gid
525 caller_gidfile = self.my_gid()
527 # the gid of the user who will be delegated to
528 delegee_gid = self.bootstrap.gid(hrn,type)
529 delegee_hrn = delegee_gid.get_hrn()
530 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
531 return dcred.save_to_string(save_parents=True)
534 # Management of the servers
539 if not hasattr (self, 'registry_proxy'):
540 self.logger.info("Contacting Registry at: %s"%self.reg_url)
541 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
542 timeout=self.options.timeout, verbose=self.options.debug)
543 return self.registry_proxy
547 if not hasattr (self, 'sliceapi_proxy'):
548 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
549 if hasattr(self.command_options,'component') and self.command_options.component:
550 # resolve the hrn at the registry
551 node_hrn = self.command_options.component
552 records = self.registry().Resolve(node_hrn, self.my_credential_string)
553 records = filter_records('node', records)
555 self.logger.warning("No such component:%r"% opts.component)
557 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
558 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
560 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
561 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
562 self.sm_url = 'http://' + self.sm_url
563 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
564 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
565 timeout=self.options.timeout, verbose=self.options.debug)
566 return self.sliceapi_proxy
568 def get_cached_server_version(self, server):
569 # check local cache first
572 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
573 cache_key = server.url + "-version"
575 cache = Cache(cache_file)
578 self.logger.info("Local cache not found at: %s" % cache_file)
581 version = cache.get(cache_key)
584 result = server.GetVersion()
585 version= ReturnValue.get_value(result)
586 # cache version for 20 minutes
587 cache.add(cache_key, version, ttl= 60*20)
588 self.logger.info("Updating cache file %s" % cache_file)
589 cache.save_to_file(cache_file)
593 ### resurrect this temporarily so we can support V1 aggregates for a while
594 def server_supports_options_arg(self, server):
596 Returns true if server support the optional call_id arg, false otherwise.
598 server_version = self.get_cached_server_version(server)
600 # xxx need to rewrite this
601 if int(server_version.get('geni_api')) >= 2:
605 def server_supports_call_id_arg(self, server):
606 server_version = self.get_cached_server_version(server)
608 if 'sfa' in server_version and 'code_tag' in server_version:
609 code_tag = server_version['code_tag']
610 code_tag_parts = code_tag.split("-")
611 version_parts = code_tag_parts[0].split(".")
612 major, minor = version_parts[0], version_parts[1]
613 rev = code_tag_parts[1]
614 if int(major) == 1 and minor == 0 and build >= 22:
618 ### ois = options if supported
619 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
620 def ois (self, server, option_dict):
621 if self.server_supports_options_arg (server):
623 elif self.server_supports_call_id_arg (server):
624 return [ unique_call_id () ]
628 ### cis = call_id if supported - like ois
629 def cis (self, server):
630 if self.server_supports_call_id_arg (server):
631 return [ unique_call_id ]
635 ######################################## miscell utilities
636 def get_rspec_file(self, rspec):
637 if (os.path.isabs(rspec)):
640 file = os.path.join(self.options.sfi_dir, rspec)
641 if (os.path.isfile(file)):
644 self.logger.critical("No such rspec file %s"%rspec)
647 def get_record_file(self, record):
648 if (os.path.isabs(record)):
651 file = os.path.join(self.options.sfi_dir, record)
652 if (os.path.isfile(file)):
655 self.logger.critical("No such registry record file %s"%record)
659 #==========================================================================
660 # Following functions implement the commands
662 # Registry-related commands
663 #==========================================================================
665 def version(self, options, args):
667 display an SFA server version (GetVersion)
668 or version information about sfi itself
670 if options.version_local:
671 version=version_core()
673 if options.version_registry:
674 server=self.registry()
676 server = self.sliceapi()
677 result = server.GetVersion()
678 version = ReturnValue.get_value(result)
680 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
682 pprinter = PrettyPrinter(indent=4)
683 pprinter.pprint(version)
685 def list(self, options, args):
687 list entries in named authority registry (List)
694 list = self.registry().List(hrn, self.my_credential_string)
696 raise Exception, "Not enough parameters for the 'list' command"
698 # filter on person, slice, site, node, etc.
699 # THis really should be in the self.filter_records funct def comment...
700 list = filter_records(options.type, list)
702 print "%s (%s)" % (record['hrn'], record['type'])
704 save_records_to_file(options.file, list, options.fileformat)
707 def show(self, options, args):
709 show details about named registry record (Resolve)
715 record_dicts = self.registry().Resolve(hrn, self.my_credential_string)
716 record_dicts = filter_records(options.type, record_dicts)
718 self.logger.error("No record of type %s"% options.type)
719 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
720 for record in records:
721 if (options.format == "text"): record.dump()
722 else: print record.save_as_xml()
724 save_records_to_file(options.file, record_dicts, options.fileformat)
727 def add(self, options, args):
728 "add record into registry from xml file (Register)"
729 auth_cred = self.my_authority_credential_string()
733 record_filepath = args[0]
734 rec_file = self.get_record_file(record_filepath)
735 record = load_record_from_file(rec_file).todict()
736 return self.registry().Register(record, auth_cred)
738 def update(self, options, args):
739 "update record into registry from xml file (Update)"
743 rec_file = self.get_record_file(args[0])
744 record = load_record_from_file(rec_file)
745 if record.type == "user":
746 if record.hrn == self.user:
747 cred = self.my_credential_string
749 cred = self.my_authority_credential_string()
750 elif record.type in ["slice"]:
752 cred = self.slice_credential_string(record.hrn)
753 except ServerException, e:
754 # XXX smbaker -- once we have better error return codes, update this
755 # to do something better than a string compare
756 if "Permission error" in e.args[0]:
757 cred = self.my_authority_credential_string()
760 elif record.type in ["authority"]:
761 cred = self.my_authority_credential_string()
762 elif record.type == 'node':
763 cred = self.my_authority_credential_string()
765 raise "unknown record type" + record.type
766 record_dict = record.todict()
767 return self.registry().Update(record_dict, cred)
769 def remove(self, options, args):
770 "remove registry record by name (Remove)"
771 auth_cred = self.my_authority_credential_string()
779 return self.registry().Remove(hrn, auth_cred, type)
781 # ==================================================================
782 # Slice-related commands
783 # ==================================================================
785 def slices(self, options, args):
786 "list instantiated slices (ListSlices) - returns urn's"
787 server = self.sliceapi()
789 creds = [self.my_credential_string]
791 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
792 creds.append(delegated_cred)
793 # options and call_id when supported
795 api_options['call_id']=unique_call_id()
796 result = server.ListSlices(creds, *self.ois(server,api_options))
797 value = ReturnValue.get_value(result)
799 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
804 # show rspec for named slice
805 def resources(self, options, args):
807 with no arg, discover available resources, (ListResources)
808 or with an slice hrn, shows currently provisioned resources
810 server = self.sliceapi()
815 creds.append(self.slice_credential_string(args[0]))
817 creds.append(self.my_credential_string)
819 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
821 # no need to check if server accepts the options argument since the options has
822 # been a required argument since v1 API
824 # always send call_id to v2 servers
825 api_options ['call_id'] = unique_call_id()
826 # ask for cached value if available
827 api_options ['cached'] = True
830 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
832 api_options['info'] = options.info
834 if options.current == True:
835 api_options['cached'] = False
837 api_options['cached'] = True
838 if options.rspec_version:
839 version_manager = VersionManager()
840 server_version = self.get_cached_server_version(server)
841 if 'sfa' in server_version:
842 # just request the version the client wants
843 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
845 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
847 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
848 result = server.ListResources (creds, api_options)
849 value = ReturnValue.get_value(result)
851 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
852 if options.file is not None:
853 save_rspec_to_file(value, options.file)
854 if (self.options.raw is None) and (options.file is None):
855 display_rspec(value, options.format)
859 def create(self, options, args):
861 create or update named slice with given rspec
863 server = self.sliceapi()
865 # xxx do we need to check usage (len(args)) ?
868 slice_urn = hrn_to_urn(slice_hrn, 'slice')
871 creds = [self.slice_credential_string(slice_hrn)]
872 delegated_cred = None
873 server_version = self.get_cached_server_version(server)
874 if server_version.get('interface') == 'slicemgr':
875 # delegate our cred to the slice manager
876 # do not delegate cred to slicemgr...not working at the moment
878 #if server_version.get('hrn'):
879 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
880 #elif server_version.get('urn'):
881 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
884 rspec_file = self.get_rspec_file(args[1])
885 rspec = open(rspec_file).read()
888 # need to pass along user keys to the aggregate.
890 # { urn: urn:publicid:IDN+emulab.net+user+alice
891 # keys: [<ssh key A>, <ssh key B>]
894 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
895 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
896 slice_record = slice_records[0]
897 user_hrns = slice_record['researcher']
898 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
899 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
901 if 'sfa' not in server_version:
902 users = pg_users_arg(user_records)
904 rspec.filter({'component_manager_id': server_version['urn']})
905 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
907 users = sfa_users_arg(user_records, slice_record)
909 # do not append users, keys, or slice tags. Anything
910 # not contained in this request will be removed from the slice
912 # CreateSliver has supported the options argument for a while now so it should
913 # be safe to assume this server support it
915 api_options ['append'] = False
916 api_options ['call_id'] = unique_call_id()
917 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
918 value = ReturnValue.get_value(result)
920 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
921 if options.file is not None:
922 save_rspec_to_file (value, options.file)
923 if (self.options.raw is None) and (options.file is None):
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 value = ReturnValue.get_value(result)
951 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
956 def status(self, options, args):
958 retrieve slice status (SliverStatus)
960 server = self.sliceapi()
964 slice_urn = hrn_to_urn(slice_hrn, 'slice')
967 slice_cred = self.slice_credential_string(slice_hrn)
970 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
971 creds.append(delegated_cred)
973 # options and call_id when supported
975 api_options['call_id']=unique_call_id()
976 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
977 value = ReturnValue.get_value(result)
979 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
983 def start(self, options, args):
985 start named slice (Start)
987 server = self.sliceapi()
991 slice_urn = hrn_to_urn(slice_hrn, 'slice')
994 slice_cred = self.slice_credential_string(args[0])
997 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
998 creds.append(delegated_cred)
999 # xxx Thierry - does this not need an api_options as well ?
1000 result = server.Start(slice_urn, creds)
1001 value = ReturnValue.get_value(result)
1002 if self.options.raw:
1003 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1008 def stop(self, options, args):
1010 stop named slice (Stop)
1012 server = self.sliceapi()
1015 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1017 slice_cred = self.slice_credential_string(args[0])
1018 creds = [slice_cred]
1019 if options.delegate:
1020 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1021 creds.append(delegated_cred)
1022 result = server.Stop(slice_urn, creds)
1023 value = ReturnValue.get_value(result)
1024 if self.options.raw:
1025 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1031 def reset(self, options, args):
1033 reset named slice (reset_slice)
1035 server = self.sliceapi()
1038 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1040 slice_cred = self.slice_credential_string(args[0])
1041 creds = [slice_cred]
1042 if options.delegate:
1043 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1044 creds.append(delegated_cred)
1045 result = server.reset_slice(creds, slice_urn)
1046 value = ReturnValue.get_value(result)
1047 if self.options.raw:
1048 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1053 def renew(self, options, args):
1055 renew slice (RenewSliver)
1057 server = self.sliceapi()
1060 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1062 slice_cred = self.slice_credential_string(args[0])
1063 creds = [slice_cred]
1064 if options.delegate:
1065 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1066 creds.append(delegated_cred)
1069 # options and call_id when supported
1071 api_options['call_id']=unique_call_id()
1072 result = server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1073 value = ReturnValue.get_value(result)
1074 if self.options.raw:
1075 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1081 def shutdown(self, options, args):
1083 shutdown named slice (Shutdown)
1085 server = self.sliceapi()
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)
1095 result = server.Shutdown(slice_urn, creds)
1096 value = ReturnValue.get_value(result)
1097 if self.options.raw:
1098 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1104 def get_ticket(self, options, args):
1106 get a ticket for the specified slice
1108 server = self.sliceapi()
1110 slice_hrn, rspec_path = args[0], args[1]
1111 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1113 slice_cred = self.slice_credential_string(slice_hrn)
1114 creds = [slice_cred]
1115 if options.delegate:
1116 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1117 creds.append(delegated_cred)
1119 rspec_file = self.get_rspec_file(rspec_path)
1120 rspec = open(rspec_file).read()
1121 # options and call_id when supported
1123 api_options['call_id']=unique_call_id()
1124 # get ticket at the server
1125 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1127 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1128 self.logger.info("writing ticket to %s"%file)
1129 ticket = SfaTicket(string=ticket_string)
1130 ticket.save_to_file(filename=file, save_parents=True)
1132 def redeem_ticket(self, options, args):
1134 Connects to nodes in a slice and redeems a ticket
1135 (slice hrn is retrieved from the ticket)
1137 ticket_file = args[0]
1139 # get slice hrn from the ticket
1140 # use this to get the right slice credential
1141 ticket = SfaTicket(filename=ticket_file)
1143 ticket_string = ticket.save_to_string(save_parents=True)
1145 slice_hrn = ticket.gidObject.get_hrn()
1146 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1147 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1148 slice_cred = self.slice_credential_string(slice_hrn)
1150 # get a list of node hostnames from the RSpec
1151 tree = etree.parse(StringIO(ticket.rspec))
1152 root = tree.getroot()
1153 hostnames = root.xpath("./network/site/node/hostname/text()")
1155 # create an xmlrpc connection to the component manager at each of these
1156 # components and gall redeem_ticket
1158 for hostname in hostnames:
1160 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1161 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1162 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1163 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1164 timeout=self.options.timeout, verbose=self.options.debug)
1165 server.RedeemTicket(ticket_string, slice_cred)
1166 self.logger.info("Success")
1167 except socket.gaierror:
1168 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1169 except Exception, e:
1170 self.logger.log_exc(e.message)
1173 def create_gid(self, options, args):
1175 Create a GID (CreateGid)
1180 target_hrn = args[0]
1181 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.bootstrap.my_gid_string())
1183 filename = options.file
1185 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1186 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1187 GID(string=gid).save_to_file(filename)
1190 def delegate(self, options, args):
1192 (locally) create delegate credential for use by given hrn
1194 delegee_hrn = args[0]
1195 if options.delegate_user:
1196 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1197 elif options.delegate_slice:
1198 slice_cred = self.slice_credential_string(options.delegate_slice)
1199 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1201 self.logger.warning("Must specify either --user or --slice <hrn>")
1203 delegated_cred = Credential(string=cred)
1204 object_hrn = delegated_cred.get_gid_object().get_hrn()
1205 if options.delegate_user:
1206 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1207 + get_leaf(object_hrn) + ".cred")
1208 elif options.delegate_slice:
1209 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1210 + get_leaf(object_hrn) + ".cred")
1212 delegated_cred.save_to_file(dest_fn, save_parents=True)
1214 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1216 def get_trusted_certs(self, options, args):
1218 return uhe trusted certs at this interface (get_trusted_certs)
1220 trusted_certs = self.registry().get_trusted_certs()
1221 for trusted_cert in trusted_certs:
1222 gid = GID(string=trusted_cert)
1224 cert = Certificate(string=trusted_cert)
1225 self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())