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("-R", "--raw", dest="raw", action="store_true", default=False,
336 help="Display raw, unparsed server response")
337 parser.add_option("-d", "--dir", dest="sfi_dir",
338 help="config & working directory - default is %default",
339 metavar="PATH", default=Sfi.default_sfi_dir())
340 parser.add_option("-u", "--user", dest="user",
341 help="user name", metavar="HRN", default=None)
342 parser.add_option("-a", "--auth", dest="auth",
343 help="authority name", metavar="HRN", default=None)
344 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
345 help="verbose mode - cumulative")
346 parser.add_option("-D", "--debug",
347 action="store_true", dest="debug", default=False,
348 help="Debug (xml-rpc) protocol messages")
349 # would it make sense to use ~/.ssh/id_rsa as a default here ?
350 parser.add_option("-k", "--private-key",
351 action="store", dest="user_private_key", default=None,
352 help="point to the private key file to use if not yet installed in sfi_dir")
353 parser.add_option("-t", "--timeout", dest="timeout", default=None,
354 help="Amout of time to wait before timing out the request")
355 parser.add_option("-?", "--commands",
356 action="store_true", dest="command_help", default=False,
357 help="one page summary on commands & exit")
358 parser.disable_interspersed_args()
363 def print_help (self):
364 self.sfi_parser.print_help()
365 self.command_parser.print_help()
368 # Main: parse arguments and dispatch to command
370 def dispatch(self, command, command_options, command_args):
371 return getattr(self, command)(command_options, command_args)
374 self.sfi_parser = self.create_parser()
375 (options, args) = self.sfi_parser.parse_args()
376 if options.command_help:
377 self.print_command_help(options)
379 self.options = options
381 self.logger.setLevelFromOptVerbose(self.options.verbose)
384 self.logger.critical("No command given. Use -h for help.")
385 self.print_command_help(options)
389 self.command_parser = self.create_command_parser(command)
390 (command_options, command_args) = self.command_parser.parse_args(args[1:])
391 self.command_options = command_options
395 self.logger.info("Command=%s" % command)
398 self.dispatch(command, command_options, command_args)
400 self.logger.critical ("Unknown command %s"%command)
407 def read_config(self):
408 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
410 config = Config (config_file)
412 self.logger.critical("Failed to read configuration file %s"%config_file)
413 self.logger.info("Make sure to remove the export clauses and to add quotes")
414 if self.options.verbose==0:
415 self.logger.info("Re-run with -v for more details")
417 self.logger.log_exc("Could not read config file %s"%config_file)
422 if (self.options.sm is not None):
423 self.sm_url = self.options.sm
424 elif hasattr(config, "SFI_SM"):
425 self.sm_url = config.SFI_SM
427 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
431 if (self.options.registry is not None):
432 self.reg_url = self.options.registry
433 elif hasattr(config, "SFI_REGISTRY"):
434 self.reg_url = config.SFI_REGISTRY
436 self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
440 if (self.options.user is not None):
441 self.user = self.options.user
442 elif hasattr(config, "SFI_USER"):
443 self.user = config.SFI_USER
445 self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
449 if (self.options.auth is not None):
450 self.authority = self.options.auth
451 elif hasattr(config, "SFI_AUTH"):
452 self.authority = config.SFI_AUTH
454 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
461 # Get various credential and spec files
463 # Establishes limiting conventions
464 # - conflates MAs and SAs
465 # - assumes last token in slice name is unique
467 # Bootstraps credentials
468 # - bootstrap user credential from self-signed certificate
469 # - bootstrap authority credential from user credential
470 # - bootstrap slice credential from user credential
473 # init self-signed cert, user credentials and gid
474 def bootstrap (self):
475 bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
476 # if -k is provided, use this to initialize private key
477 if self.options.user_private_key:
478 bootstrap.init_private_key_if_missing (self.options.user_private_key)
480 # trigger legacy compat code if needed
481 # the name has changed from just <leaf>.pkey to <hrn>.pkey
482 if not os.path.isfile(bootstrap.private_key_filename()):
483 self.logger.info ("private key not found, trying legacy name")
485 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
486 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
487 bootstrap.init_private_key_if_missing (legacy_private_key)
488 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
490 self.logger.log_exc("Can't find private key ")
494 bootstrap.bootstrap_my_gid()
495 # extract what's needed
496 self.private_key = bootstrap.private_key()
497 self.my_credential_string = bootstrap.my_credential_string ()
498 self.my_gid = bootstrap.my_gid ()
499 self.bootstrap = bootstrap
502 def my_authority_credential_string(self):
503 if not self.authority:
504 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
506 return self.bootstrap.authority_credential_string (self.authority)
508 def slice_credential_string(self, name):
509 return self.bootstrap.slice_credential_string (name)
511 # xxx should be supported by sfaclientbootstrap as well
512 def delegate_cred(self, object_cred, hrn, type='authority'):
513 # the gid and hrn of the object we are delegating
514 if isinstance(object_cred, str):
515 object_cred = Credential(string=object_cred)
516 object_gid = object_cred.get_gid_object()
517 object_hrn = object_gid.get_hrn()
519 if not object_cred.get_privileges().get_all_delegate():
520 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
523 # the delegating user's gid
524 caller_gidfile = self.my_gid()
526 # the gid of the user who will be delegated to
527 delegee_gid = self.bootstrap.gid(hrn,type)
528 delegee_hrn = delegee_gid.get_hrn()
529 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
530 return dcred.save_to_string(save_parents=True)
533 # Management of the servers
538 if not hasattr (self, 'registry_proxy'):
539 self.logger.info("Contacting Registry at: %s"%self.reg_url)
540 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
541 timeout=self.options.timeout, verbose=self.options.debug)
542 return self.registry_proxy
546 if not hasattr (self, 'sliceapi_proxy'):
547 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
548 if hasattr(self.command_options,'component') and self.command_options.component:
549 # resolve the hrn at the registry
550 node_hrn = self.command_options.component
551 records = self.registry().Resolve(node_hrn, self.my_credential_string)
552 records = filter_records('node', records)
554 self.logger.warning("No such component:%r"% opts.component)
556 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
557 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
559 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
560 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
561 self.sm_url = 'http://' + self.sm_url
562 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
563 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
564 timeout=self.options.timeout, verbose=self.options.debug)
565 return self.sliceapi_proxy
567 def get_cached_server_version(self, server):
568 # check local cache first
571 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
572 cache_key = server.url + "-version"
574 cache = Cache(cache_file)
577 self.logger.info("Local cache not found at: %s" % cache_file)
580 version = cache.get(cache_key)
583 result = server.GetVersion()
584 version= ReturnValue.get_value(result)
585 # cache version for 20 minutes
586 cache.add(cache_key, version, ttl= 60*20)
587 self.logger.info("Updating cache file %s" % cache_file)
588 cache.save_to_file(cache_file)
592 ### resurrect this temporarily so we can support V1 aggregates for a while
593 def server_supports_options_arg(self, server):
595 Returns true if server support the optional call_id arg, false otherwise.
597 server_version = self.get_cached_server_version(server)
599 # xxx need to rewrite this
600 if int(server_version.get('geni_api')) >= 2:
604 def server_supports_call_id_arg(self, server):
605 server_version = self.get_cached_server_version(server)
607 if 'sfa' in server_version and 'code_tag' in server_version:
608 code_tag = server_version['code_tag']
609 code_tag_parts = code_tag.split("-")
610 version_parts = code_tag_parts[0].split(".")
611 major, minor = version_parts[0], version_parts[1]
612 rev = code_tag_parts[1]
613 if int(major) == 1 and minor == 0 and build >= 22:
617 ### ois = options if supported
618 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
619 def ois (self, server, option_dict):
620 if self.server_supports_options_arg (server):
622 elif self.server_supports_call_id_arg (server):
623 return [ unique_call_id () ]
627 ### cis = call_id if supported - like ois
628 def cis (self, server):
629 if self.server_supports_call_id_arg (server):
630 return [ unique_call_id ]
634 ######################################## miscell utilities
635 def get_rspec_file(self, rspec):
636 if (os.path.isabs(rspec)):
639 file = os.path.join(self.options.sfi_dir, rspec)
640 if (os.path.isfile(file)):
643 self.logger.critical("No such rspec file %s"%rspec)
646 def get_record_file(self, record):
647 if (os.path.isabs(record)):
650 file = os.path.join(self.options.sfi_dir, record)
651 if (os.path.isfile(file)):
654 self.logger.critical("No such registry record file %s"%record)
658 #==========================================================================
659 # Following functions implement the commands
661 # Registry-related commands
662 #==========================================================================
664 def version(self, options, args):
666 display an SFA server version (GetVersion)
667 or version information about sfi itself
669 if options.version_local:
670 version=version_core()
672 if options.version_registry:
673 server=self.registry()
675 server = self.sliceapi()
676 result = server.GetVersion()
677 version = ReturnValue.get_value(result)
678 pprinter = PrettyPrinter(indent=4)
679 pprinter.pprint(version)
681 save_variable_to_file(version, options.file, options.fileformat)
683 def list(self, options, args):
685 list entries in named authority registry (List)
692 list = self.registry().List(hrn, self.my_credential_string)
694 raise Exception, "Not enough parameters for the 'list' command"
696 # filter on person, slice, site, node, etc.
697 # THis really should be in the self.filter_records funct def comment...
698 list = filter_records(options.type, list)
700 print "%s (%s)" % (record['hrn'], record['type'])
702 save_records_to_file(options.file, list, options.fileformat)
705 def show(self, options, args):
707 show details about named registry record (Resolve)
713 records = self.registry().Resolve(hrn, self.my_credential_string)
714 records = filter_records(options.type, records)
716 self.logger.error("No record of type %s"% options.type)
717 for record in records:
718 if record['type'] in ['user']:
719 record = UserRecord(dict=record)
720 elif record['type'] in ['slice']:
721 record = SliceRecord(dict=record)
722 elif record['type'] in ['node']:
723 record = NodeRecord(dict=record)
724 elif record['type'].startswith('authority'):
725 record = AuthorityRecord(dict=record)
727 record = SfaRecord(dict=record)
728 if (options.format == "text"):
731 print record.save_to_string()
733 save_records_to_file(options.file, records, options.fileformat)
736 def add(self, options, args):
737 "add record into registry from xml file (Register)"
738 auth_cred = self.my_authority_credential_string()
742 record_filepath = args[0]
743 rec_file = self.get_record_file(record_filepath)
744 record = load_record_from_file(rec_file).as_dict()
745 return self.registry().Register(record, auth_cred)
747 def update(self, options, args):
748 "update record into registry from xml file (Update)"
752 rec_file = self.get_record_file(args[0])
753 record = load_record_from_file(rec_file)
754 if record['type'] == "user":
755 if record.get_name() == self.user:
756 cred = self.my_credential_string
758 cred = self.my_authority_credential_string()
759 elif record['type'] in ["slice"]:
761 cred = self.slice_credential_string(record.get_name())
762 except ServerException, e:
763 # XXX smbaker -- once we have better error return codes, update this
764 # to do something better than a string compare
765 if "Permission error" in e.args[0]:
766 cred = self.my_authority_credential_string()
769 elif record.get_type() in ["authority"]:
770 cred = self.my_authority_credential_string()
771 elif record.get_type() == 'node':
772 cred = self.my_authority_credential_string()
774 raise "unknown record type" + record.get_type()
775 record = record.as_dict()
776 return self.registry().Update(record, cred)
778 def remove(self, options, args):
779 "remove registry record by name (Remove)"
780 auth_cred = self.my_authority_credential_string()
788 return self.registry().Remove(hrn, auth_cred, type)
790 # ==================================================================
791 # Slice-related commands
792 # ==================================================================
794 def slices(self, options, args):
795 "list instantiated slices (ListSlices) - returns urn's"
796 server = self.sliceapi()
798 creds = [self.my_credential_string]
800 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
801 creds.append(delegated_cred)
802 # options and call_id when supported
804 api_options['call_id']=unique_call_id()
805 result = server.ListSlices(creds, *self.ois(server,api_options))
806 value = ReturnValue.get_value(result)
813 # show rspec for named slice
814 def resources(self, options, args):
816 with no arg, discover available resources, (ListResources)
817 or with an slice hrn, shows currently provisioned resources
819 server = self.sliceapi()
824 creds.append(self.slice_credential_string(args[0]))
826 creds.append(self.my_credential_string)
828 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
830 # no need to check if server accepts the options argument since the options has
831 # been a required argument since v1 API
833 # always send call_id to v2 servers
834 api_options ['call_id'] = unique_call_id()
835 # ask for cached value if available
836 api_options ['cached'] = True
839 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
841 api_options['info'] = options.info
843 if options.current == True:
844 api_options['cached'] = False
846 api_options['cached'] = True
847 if options.rspec_version:
848 version_manager = VersionManager()
849 server_version = self.get_cached_server_version(server)
850 if 'sfa' in server_version:
851 # just request the version the client wants
852 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
854 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
856 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
857 result = server.ListResources (creds, api_options)
858 value = ReturnValue.get_value(result)
859 if options.file is None:
863 display_rspec(value, options.format)
865 save_rspec_to_file(value, options.file)
868 def create(self, options, args):
870 create or update named slice with given rspec
872 server = self.sliceapi()
874 # xxx do we need to check usage (len(args)) ?
877 slice_urn = hrn_to_urn(slice_hrn, 'slice')
880 creds = [self.slice_credential_string(slice_hrn)]
881 delegated_cred = None
882 server_version = self.get_cached_server_version(server)
883 if server_version.get('interface') == 'slicemgr':
884 # delegate our cred to the slice manager
885 # do not delegate cred to slicemgr...not working at the moment
887 #if server_version.get('hrn'):
888 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
889 #elif server_version.get('urn'):
890 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
893 rspec_file = self.get_rspec_file(args[1])
894 rspec = open(rspec_file).read()
897 # need to pass along user keys to the aggregate.
899 # { urn: urn:publicid:IDN+emulab.net+user+alice
900 # keys: [<ssh key A>, <ssh key B>]
903 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
904 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
905 slice_record = slice_records[0]
906 user_hrns = slice_record['researcher']
907 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
908 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
910 if 'sfa' not in server_version:
911 users = pg_users_arg(user_records)
913 rspec.filter({'component_manager_id': server_version['urn']})
914 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
916 users = sfa_users_arg(user_records, slice_record)
918 # do not append users, keys, or slice tags. Anything
919 # not contained in this request will be removed from the slice
921 # CreateSliver has supported the options argument for a while now so it should
922 # be safe to assume this server support it
924 api_options ['append'] = False
925 api_options ['call_id'] = unique_call_id()
927 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
928 value = ReturnValue.get_value(result)
929 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 value = ReturnValue.get_value(result)
966 def status(self, options, args):
968 retrieve slice status (SliverStatus)
970 server = self.sliceapi()
974 slice_urn = hrn_to_urn(slice_hrn, 'slice')
977 slice_cred = self.slice_credential_string(slice_hrn)
980 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
981 creds.append(delegated_cred)
983 # options and call_id when supported
985 api_options['call_id']=unique_call_id()
986 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
987 value = ReturnValue.get_value(result)
993 save_variable_to_file(value, options.file, options.fileformat)
995 def start(self, options, args):
997 start named slice (Start)
999 server = self.sliceapi()
1003 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 # xxx Thierry - does this not need an api_options as well ?
1012 result = server.Start(slice_urn, creds)
1013 value = ReturnValue.get_value(result)
1014 if self.options.raw:
1020 def stop(self, options, args):
1022 stop named slice (Stop)
1024 server = self.sliceapi()
1027 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1029 slice_cred = self.slice_credential_string(args[0])
1030 creds = [slice_cred]
1031 if options.delegate:
1032 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1033 creds.append(delegated_cred)
1034 result = server.Stop(slice_urn, creds)
1035 value = ReturnValue.get_value(result)
1036 if self.options.raw:
1043 def reset(self, options, args):
1045 reset named slice (reset_slice)
1047 server = self.sliceapi()
1050 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1052 slice_cred = self.slice_credential_string(args[0])
1053 creds = [slice_cred]
1054 if options.delegate:
1055 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1056 creds.append(delegated_cred)
1057 result = server.reset_slice(creds, slice_urn)
1058 value = ReturnValue.get_value(result)
1059 if self.options.raw:
1065 def renew(self, options, args):
1067 renew slice (RenewSliver)
1069 server = self.sliceapi()
1072 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1074 slice_cred = self.slice_credential_string(args[0])
1075 creds = [slice_cred]
1076 if options.delegate:
1077 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1078 creds.append(delegated_cred)
1081 # options and call_id when supported
1083 api_options['call_id']=unique_call_id()
1084 result = server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1085 value = ReturnValue.get_value(result)
1086 if self.options.raw:
1093 def shutdown(self, options, args):
1095 shutdown named slice (Shutdown)
1097 server = self.sliceapi()
1100 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1102 slice_cred = self.slice_credential_string(slice_hrn)
1103 creds = [slice_cred]
1104 if options.delegate:
1105 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1106 creds.append(delegated_cred)
1107 result = server.Shutdown(slice_urn, creds)
1108 value = ReturnValue.get_value(result)
1109 if self.options.raw:
1116 def get_ticket(self, options, args):
1118 get a ticket for the specified slice
1120 server = self.sliceapi()
1122 slice_hrn, rspec_path = args[0], args[1]
1123 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1125 slice_cred = self.slice_credential_string(slice_hrn)
1126 creds = [slice_cred]
1127 if options.delegate:
1128 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1129 creds.append(delegated_cred)
1131 rspec_file = self.get_rspec_file(rspec_path)
1132 rspec = open(rspec_file).read()
1133 # options and call_id when supported
1135 api_options['call_id']=unique_call_id()
1136 # get ticket at the server
1137 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1139 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1140 self.logger.info("writing ticket to %s"%file)
1141 ticket = SfaTicket(string=ticket_string)
1142 ticket.save_to_file(filename=file, save_parents=True)
1144 def redeem_ticket(self, options, args):
1146 Connects to nodes in a slice and redeems a ticket
1147 (slice hrn is retrieved from the ticket)
1149 ticket_file = args[0]
1151 # get slice hrn from the ticket
1152 # use this to get the right slice credential
1153 ticket = SfaTicket(filename=ticket_file)
1155 ticket_string = ticket.save_to_string(save_parents=True)
1157 slice_hrn = ticket.gidObject.get_hrn()
1158 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1159 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1160 slice_cred = self.slice_credential_string(slice_hrn)
1162 # get a list of node hostnames from the RSpec
1163 tree = etree.parse(StringIO(ticket.rspec))
1164 root = tree.getroot()
1165 hostnames = root.xpath("./network/site/node/hostname/text()")
1167 # create an xmlrpc connection to the component manager at each of these
1168 # components and gall redeem_ticket
1170 for hostname in hostnames:
1172 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1173 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1174 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1175 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1176 timeout=self.options.timeout, verbose=self.options.debug)
1177 server.RedeemTicket(ticket_string, slice_cred)
1178 self.logger.info("Success")
1179 except socket.gaierror:
1180 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1181 except Exception, e:
1182 self.logger.log_exc(e.message)
1185 def create_gid(self, options, args):
1187 Create a GID (CreateGid)
1192 target_hrn = args[0]
1193 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.bootstrap.my_gid_string())
1195 filename = options.file
1197 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1198 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1199 GID(string=gid).save_to_file(filename)
1202 def delegate(self, options, args):
1204 (locally) create delegate credential for use by given hrn
1206 delegee_hrn = args[0]
1207 if options.delegate_user:
1208 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1209 elif options.delegate_slice:
1210 slice_cred = self.slice_credential_string(options.delegate_slice)
1211 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1213 self.logger.warning("Must specify either --user or --slice <hrn>")
1215 delegated_cred = Credential(string=cred)
1216 object_hrn = delegated_cred.get_gid_object().get_hrn()
1217 if options.delegate_user:
1218 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1219 + get_leaf(object_hrn) + ".cred")
1220 elif options.delegate_slice:
1221 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1222 + get_leaf(object_hrn) + ".cred")
1224 delegated_cred.save_to_file(dest_fn, save_parents=True)
1226 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1228 def get_trusted_certs(self, options, args):
1230 return uhe trusted certs at this interface (get_trusted_certs)
1232 trusted_certs = self.registry().get_trusted_certs()
1233 for trusted_cert in trusted_certs:
1234 gid = GID(string=trusted_cert)
1236 cert = Certificate(string=trusted_cert)
1237 self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())