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 SfaRecord, UserRecord, SliceRecord, NodeRecord, AuthorityRecord
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, recordList, format="xml"):
125 for record in recordList:
127 save_record_to_file(filename + "." + str(index), record)
129 save_record_to_file(filename, record)
131 elif format == "xmllist":
132 f = open(filename, "w")
133 f.write("<recordlist>\n")
134 for record in recordList:
135 record = SfaRecord(dict=record)
136 f.write('<record hrn="' + record.get_name() + '" type="' + record.get_type() + '" />\n')
137 f.write("</recordlist>\n")
139 elif format == "hrnlist":
140 f = open(filename, "w")
141 for record in recordList:
142 record = SfaRecord(dict=record)
143 f.write(record.get_name() + "\n")
146 # this should never happen
147 print "unknown output format", format
149 def save_record_to_file(filename, record):
150 if record['type'] in ['user']:
151 record = UserRecord(dict=record)
152 elif record['type'] in ['slice']:
153 record = SliceRecord(dict=record)
154 elif record['type'] in ['node']:
155 record = NodeRecord(dict=record)
156 elif record['type'] in ['authority', 'ma', 'sa']:
157 record = AuthorityRecord(dict=record)
159 record = SfaRecord(dict=record)
160 str = record.save_to_string()
161 f=codecs.open(filename, encoding='utf-8',mode="w")
168 def load_record_from_file(filename):
169 f=codecs.open(filename, encoding="utf-8", mode="r")
172 record = SfaRecord(string=str)
177 def unique_call_id(): return uuid.uuid4().urn
181 # dirty hack to make this class usable from the outside
182 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
185 def default_sfi_dir ():
186 if os.path.isfile("./sfi_config"):
189 return os.path.expanduser("~/.sfi/")
191 # dummy to meet Sfi's expectations for its 'options' field
192 # i.e. s/t we can do setattr on
196 def __init__ (self,options=None):
197 if options is None: options=Sfi.DummyOptions()
198 for opt in Sfi.required_options:
199 if not hasattr(options,opt): setattr(options,opt,None)
200 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
201 self.options = options
203 self.authority = None
204 self.logger = sfi_logger
205 self.logger.enable_console()
206 self.available_names = [ tuple[0] for tuple in Sfi.available ]
207 self.available_dict = dict (Sfi.available)
209 # tuples command-name expected-args in the order in which they should appear in the help
212 ("list", "authority"),
215 ("update", "record"),
218 ("resources", "[slice_hrn]"),
219 ("create", "slice_hrn rspec"),
220 ("delete", "slice_hrn"),
221 ("status", "slice_hrn"),
222 ("start", "slice_hrn"),
223 ("stop", "slice_hrn"),
224 ("reset", "slice_hrn"),
225 ("renew", "slice_hrn time"),
226 ("shutdown", "slice_hrn"),
227 ("get_ticket", "slice_hrn rspec"),
228 ("redeem_ticket", "ticket"),
229 ("delegate", "name"),
230 ("create_gid", "[name]"),
231 ("get_trusted_certs", "cred"),
234 def print_command_help (self, options):
235 verbose=getattr(options,'verbose')
236 format3="%18s %-15s %s"
239 print format3%("command","cmd_args","description")
243 self.create_parser().print_help()
244 for command in self.available_names:
245 args=self.available_dict[command]
246 method=getattr(self,command,None)
248 if method: doc=getattr(method,'__doc__',"")
249 if not doc: doc="*** no doc found ***"
250 doc=doc.strip(" \t\n")
251 doc=doc.replace("\n","\n"+35*' ')
254 print format3%(command,args,doc)
256 self.create_command_parser(command).print_help()
258 def create_command_parser(self, command):
259 if command not in self.available_dict:
260 msg="Invalid command\n"
262 msg += ','.join(self.available_names)
263 self.logger.critical(msg)
266 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
267 % (command, self.available_dict[command]))
269 # user specifies remote aggregate/sm/component
270 if command in ("resources", "slices", "create", "delete", "start", "stop",
271 "restart", "shutdown", "get_ticket", "renew", "status"):
272 parser.add_option("-d", "--delegate", dest="delegate", default=None,
274 help="Include a credential delegated to the user's root"+\
275 "authority in set of credentials for this call")
277 # registy filter option
278 if command in ("list", "show", "remove"):
279 parser.add_option("-t", "--type", dest="type", type="choice",
280 help="type filter ([all]|user|slice|authority|node|aggregate)",
281 choices=("all", "user", "slice", "authority", "node", "aggregate"),
283 if command in ("resources"):
285 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
286 help="schema type and version of resulting RSpec")
287 # disable/enable cached rspecs
288 parser.add_option("-c", "--current", dest="current", default=False,
290 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
292 parser.add_option("-f", "--format", dest="format", type="choice",
293 help="display format ([xml]|dns|ip)", default="xml",
294 choices=("xml", "dns", "ip"))
295 #panos: a new option to define the type of information about resources a user is interested in
296 parser.add_option("-i", "--info", dest="info",
297 help="optional component information", default=None)
300 # 'create' does return the new rspec, makes sense to save that too
301 if command in ("resources", "show", "list", "create_gid", 'create'):
302 parser.add_option("-o", "--output", dest="file",
303 help="output XML to file", metavar="FILE", default=None)
305 if command in ("show", "list"):
306 parser.add_option("-f", "--format", dest="format", type="choice",
307 help="display format ([text]|xml)", default="text",
308 choices=("text", "xml"))
310 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
311 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
312 choices=("xml", "xmllist", "hrnlist"))
314 if command in ("delegate"):
315 parser.add_option("-u", "--user",
316 action="store_true", dest="delegate_user", default=False,
317 help="delegate user credential")
318 parser.add_option("-s", "--slice", dest="delegate_slice",
319 help="delegate slice credential", metavar="HRN", default=None)
321 if command in ("version"):
322 parser.add_option("-R","--registry-version",
323 action="store_true", dest="version_registry", default=False,
324 help="probe registry version instead of sliceapi")
325 parser.add_option("-l","--local",
326 action="store_true", dest="version_local", default=False,
327 help="display version of the local client")
332 def create_parser(self):
334 # Generate command line parser
335 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
336 description="Commands: %s"%(" ".join(self.available_names)))
337 parser.add_option("-r", "--registry", dest="registry",
338 help="root registry", metavar="URL", default=None)
339 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
340 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
341 parser.add_option("-R", "--raw", dest="raw", default=None,
342 help="Save raw, unparsed server response to a file")
343 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
344 help="raw file format ([text]|pickled|json)", default="text",
345 choices=("text","pickled","json"))
346 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
347 help="text string to write before and after raw output")
348 parser.add_option("-d", "--dir", dest="sfi_dir",
349 help="config & working directory - default is %default",
350 metavar="PATH", default=Sfi.default_sfi_dir())
351 parser.add_option("-u", "--user", dest="user",
352 help="user name", metavar="HRN", default=None)
353 parser.add_option("-a", "--auth", dest="auth",
354 help="authority name", metavar="HRN", default=None)
355 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
356 help="verbose mode - cumulative")
357 parser.add_option("-D", "--debug",
358 action="store_true", dest="debug", default=False,
359 help="Debug (xml-rpc) protocol messages")
360 # would it make sense to use ~/.ssh/id_rsa as a default here ?
361 parser.add_option("-k", "--private-key",
362 action="store", dest="user_private_key", default=None,
363 help="point to the private key file to use if not yet installed in sfi_dir")
364 parser.add_option("-t", "--timeout", dest="timeout", default=None,
365 help="Amout of time to wait before timing out the request")
366 parser.add_option("-?", "--commands",
367 action="store_true", dest="command_help", default=False,
368 help="one page summary on commands & exit")
369 parser.disable_interspersed_args()
374 def print_help (self):
375 self.sfi_parser.print_help()
376 self.command_parser.print_help()
379 # Main: parse arguments and dispatch to command
381 def dispatch(self, command, command_options, command_args):
382 return getattr(self, command)(command_options, command_args)
385 self.sfi_parser = self.create_parser()
386 (options, args) = self.sfi_parser.parse_args()
387 if options.command_help:
388 self.print_command_help(options)
390 self.options = options
392 self.logger.setLevelFromOptVerbose(self.options.verbose)
395 self.logger.critical("No command given. Use -h for help.")
396 self.print_command_help(options)
400 self.command_parser = self.create_command_parser(command)
401 (command_options, command_args) = self.command_parser.parse_args(args[1:])
402 self.command_options = command_options
406 self.logger.info("Command=%s" % command)
409 self.dispatch(command, command_options, command_args)
411 self.logger.critical ("Unknown command %s"%command)
418 def read_config(self):
419 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
421 config = Config (config_file)
423 self.logger.critical("Failed to read configuration file %s"%config_file)
424 self.logger.info("Make sure to remove the export clauses and to add quotes")
425 if self.options.verbose==0:
426 self.logger.info("Re-run with -v for more details")
428 self.logger.log_exc("Could not read config file %s"%config_file)
433 if (self.options.sm is not None):
434 self.sm_url = self.options.sm
435 elif hasattr(config, "SFI_SM"):
436 self.sm_url = config.SFI_SM
438 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
442 if (self.options.registry is not None):
443 self.reg_url = self.options.registry
444 elif hasattr(config, "SFI_REGISTRY"):
445 self.reg_url = config.SFI_REGISTRY
447 self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
451 if (self.options.user is not None):
452 self.user = self.options.user
453 elif hasattr(config, "SFI_USER"):
454 self.user = config.SFI_USER
456 self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
460 if (self.options.auth is not None):
461 self.authority = self.options.auth
462 elif hasattr(config, "SFI_AUTH"):
463 self.authority = config.SFI_AUTH
465 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
472 # Get various credential and spec files
474 # Establishes limiting conventions
475 # - conflates MAs and SAs
476 # - assumes last token in slice name is unique
478 # Bootstraps credentials
479 # - bootstrap user credential from self-signed certificate
480 # - bootstrap authority credential from user credential
481 # - bootstrap slice credential from user credential
484 # init self-signed cert, user credentials and gid
485 def bootstrap (self):
486 bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
487 # if -k is provided, use this to initialize private key
488 if self.options.user_private_key:
489 bootstrap.init_private_key_if_missing (self.options.user_private_key)
491 # trigger legacy compat code if needed
492 # the name has changed from just <leaf>.pkey to <hrn>.pkey
493 if not os.path.isfile(bootstrap.private_key_filename()):
494 self.logger.info ("private key not found, trying legacy name")
496 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
497 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
498 bootstrap.init_private_key_if_missing (legacy_private_key)
499 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
501 self.logger.log_exc("Can't find private key ")
505 bootstrap.bootstrap_my_gid()
506 # extract what's needed
507 self.private_key = bootstrap.private_key()
508 self.my_credential_string = bootstrap.my_credential_string ()
509 self.my_gid = bootstrap.my_gid ()
510 self.bootstrap = bootstrap
513 def my_authority_credential_string(self):
514 if not self.authority:
515 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
517 return self.bootstrap.authority_credential_string (self.authority)
519 def slice_credential_string(self, name):
520 return self.bootstrap.slice_credential_string (name)
522 # xxx should be supported by sfaclientbootstrap as well
523 def delegate_cred(self, object_cred, hrn, type='authority'):
524 # the gid and hrn of the object we are delegating
525 if isinstance(object_cred, str):
526 object_cred = Credential(string=object_cred)
527 object_gid = object_cred.get_gid_object()
528 object_hrn = object_gid.get_hrn()
530 if not object_cred.get_privileges().get_all_delegate():
531 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
534 # the delegating user's gid
535 caller_gidfile = self.my_gid()
537 # the gid of the user who will be delegated to
538 delegee_gid = self.bootstrap.gid(hrn,type)
539 delegee_hrn = delegee_gid.get_hrn()
540 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
541 return dcred.save_to_string(save_parents=True)
544 # Management of the servers
549 if not hasattr (self, 'registry_proxy'):
550 self.logger.info("Contacting Registry at: %s"%self.reg_url)
551 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
552 timeout=self.options.timeout, verbose=self.options.debug)
553 return self.registry_proxy
557 if not hasattr (self, 'sliceapi_proxy'):
558 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
559 if hasattr(self.command_options,'component') and self.command_options.component:
560 # resolve the hrn at the registry
561 node_hrn = self.command_options.component
562 records = self.registry().Resolve(node_hrn, self.my_credential_string)
563 records = filter_records('node', records)
565 self.logger.warning("No such component:%r"% opts.component)
567 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
568 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
570 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
571 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
572 self.sm_url = 'http://' + self.sm_url
573 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
574 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
575 timeout=self.options.timeout, verbose=self.options.debug)
576 return self.sliceapi_proxy
578 def get_cached_server_version(self, server):
579 # check local cache first
582 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
583 cache_key = server.url + "-version"
585 cache = Cache(cache_file)
588 self.logger.info("Local cache not found at: %s" % cache_file)
591 version = cache.get(cache_key)
594 result = server.GetVersion()
595 version= ReturnValue.get_value(result)
596 # cache version for 20 minutes
597 cache.add(cache_key, version, ttl= 60*20)
598 self.logger.info("Updating cache file %s" % cache_file)
599 cache.save_to_file(cache_file)
603 ### resurrect this temporarily so we can support V1 aggregates for a while
604 def server_supports_options_arg(self, server):
606 Returns true if server support the optional call_id arg, false otherwise.
608 server_version = self.get_cached_server_version(server)
610 # xxx need to rewrite this
611 if int(server_version.get('geni_api')) >= 2:
615 def server_supports_call_id_arg(self, server):
616 server_version = self.get_cached_server_version(server)
618 if 'sfa' in server_version and 'code_tag' in server_version:
619 code_tag = server_version['code_tag']
620 code_tag_parts = code_tag.split("-")
621 version_parts = code_tag_parts[0].split(".")
622 major, minor = version_parts[0], version_parts[1]
623 rev = code_tag_parts[1]
624 if int(major) == 1 and minor == 0 and build >= 22:
628 ### ois = options if supported
629 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
630 def ois (self, server, option_dict):
631 if self.server_supports_options_arg (server):
633 elif self.server_supports_call_id_arg (server):
634 return [ unique_call_id () ]
638 ### cis = call_id if supported - like ois
639 def cis (self, server):
640 if self.server_supports_call_id_arg (server):
641 return [ unique_call_id ]
645 ######################################## miscell utilities
646 def get_rspec_file(self, rspec):
647 if (os.path.isabs(rspec)):
650 file = os.path.join(self.options.sfi_dir, rspec)
651 if (os.path.isfile(file)):
654 self.logger.critical("No such rspec file %s"%rspec)
657 def get_record_file(self, record):
658 if (os.path.isabs(record)):
661 file = os.path.join(self.options.sfi_dir, record)
662 if (os.path.isfile(file)):
665 self.logger.critical("No such registry record file %s"%record)
669 #==========================================================================
670 # Following functions implement the commands
672 # Registry-related commands
673 #==========================================================================
675 def version(self, options, args):
677 display an SFA server version (GetVersion)
678 or version information about sfi itself
680 if options.version_local:
681 version=version_core()
683 if options.version_registry:
684 server=self.registry()
686 server = self.sliceapi()
687 result = server.GetVersion()
688 version = ReturnValue.get_value(result)
690 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
692 pprinter = PrettyPrinter(indent=4)
693 pprinter.pprint(version)
695 def list(self, options, args):
697 list entries in named authority registry (List)
704 list = self.registry().List(hrn, self.my_credential_string)
706 raise Exception, "Not enough parameters for the 'list' command"
708 # filter on person, slice, site, node, etc.
709 # THis really should be in the self.filter_records funct def comment...
710 list = filter_records(options.type, list)
712 print "%s (%s)" % (record['hrn'], record['type'])
714 save_records_to_file(options.file, list, options.fileformat)
717 def show(self, options, args):
719 show details about named registry record (Resolve)
725 records = self.registry().Resolve(hrn, self.my_credential_string)
726 records = filter_records(options.type, records)
728 self.logger.error("No record of type %s"% options.type)
729 for record in records:
730 if record['type'] in ['user']:
731 record = UserRecord(dict=record)
732 elif record['type'] in ['slice']:
733 record = SliceRecord(dict=record)
734 elif record['type'] in ['node']:
735 record = NodeRecord(dict=record)
736 elif record['type'].startswith('authority'):
737 record = AuthorityRecord(dict=record)
739 record = SfaRecord(dict=record)
740 if (options.format == "text"):
743 print record.save_to_string()
745 save_records_to_file(options.file, records, options.fileformat)
748 def add(self, options, args):
749 "add record into registry from xml file (Register)"
750 auth_cred = self.my_authority_credential_string()
754 record_filepath = args[0]
755 rec_file = self.get_record_file(record_filepath)
756 record = load_record_from_file(rec_file).as_dict()
757 return self.registry().Register(record, auth_cred)
759 def update(self, options, args):
760 "update record into registry from xml file (Update)"
764 rec_file = self.get_record_file(args[0])
765 record = load_record_from_file(rec_file)
766 if record['type'] == "user":
767 if record.get_name() == self.user:
768 cred = self.my_credential_string
770 cred = self.my_authority_credential_string()
771 elif record['type'] in ["slice"]:
773 cred = self.slice_credential_string(record.get_name())
774 except ServerException, e:
775 # XXX smbaker -- once we have better error return codes, update this
776 # to do something better than a string compare
777 if "Permission error" in e.args[0]:
778 cred = self.my_authority_credential_string()
781 elif record.get_type() in ["authority"]:
782 cred = self.my_authority_credential_string()
783 elif record.get_type() == 'node':
784 cred = self.my_authority_credential_string()
786 raise "unknown record type" + record.get_type()
787 record = record.as_dict()
788 return self.registry().Update(record, cred)
790 def remove(self, options, args):
791 "remove registry record by name (Remove)"
792 auth_cred = self.my_authority_credential_string()
800 return self.registry().Remove(hrn, auth_cred, type)
802 # ==================================================================
803 # Slice-related commands
804 # ==================================================================
806 def slices(self, options, args):
807 "list instantiated slices (ListSlices) - returns urn's"
808 server = self.sliceapi()
810 creds = [self.my_credential_string]
812 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
813 creds.append(delegated_cred)
814 # options and call_id when supported
816 api_options['call_id']=unique_call_id()
817 result = server.ListSlices(creds, *self.ois(server,api_options))
818 value = ReturnValue.get_value(result)
820 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
825 # show rspec for named slice
826 def resources(self, options, args):
828 with no arg, discover available resources, (ListResources)
829 or with an slice hrn, shows currently provisioned resources
831 server = self.sliceapi()
836 creds.append(self.slice_credential_string(args[0]))
838 creds.append(self.my_credential_string)
840 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
842 # no need to check if server accepts the options argument since the options has
843 # been a required argument since v1 API
845 # always send call_id to v2 servers
846 api_options ['call_id'] = unique_call_id()
847 # ask for cached value if available
848 api_options ['cached'] = True
851 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
853 api_options['info'] = options.info
855 if options.current == True:
856 api_options['cached'] = False
858 api_options['cached'] = True
859 if options.rspec_version:
860 version_manager = VersionManager()
861 server_version = self.get_cached_server_version(server)
862 if 'sfa' in server_version:
863 # just request the version the client wants
864 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
866 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
868 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
869 result = server.ListResources (creds, api_options)
870 value = ReturnValue.get_value(result)
872 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
873 if options.file is not None:
874 save_rspec_to_file(value, options.file)
875 if (self.options.raw is None) and (options.file is None):
876 display_rspec(value, options.format)
880 def create(self, options, args):
882 create or update named slice with given rspec
884 server = self.sliceapi()
886 # xxx do we need to check usage (len(args)) ?
889 slice_urn = hrn_to_urn(slice_hrn, 'slice')
892 creds = [self.slice_credential_string(slice_hrn)]
893 delegated_cred = None
894 server_version = self.get_cached_server_version(server)
895 if server_version.get('interface') == 'slicemgr':
896 # delegate our cred to the slice manager
897 # do not delegate cred to slicemgr...not working at the moment
899 #if server_version.get('hrn'):
900 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
901 #elif server_version.get('urn'):
902 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
905 rspec_file = self.get_rspec_file(args[1])
906 rspec = open(rspec_file).read()
909 # need to pass along user keys to the aggregate.
911 # { urn: urn:publicid:IDN+emulab.net+user+alice
912 # keys: [<ssh key A>, <ssh key B>]
915 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
916 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
917 slice_record = slice_records[0]
918 user_hrns = slice_record['researcher']
919 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
920 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
922 if 'sfa' not in server_version:
923 users = pg_users_arg(user_records)
925 rspec.filter({'component_manager_id': server_version['urn']})
926 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
928 users = sfa_users_arg(user_records, slice_record)
930 # do not append users, keys, or slice tags. Anything
931 # not contained in this request will be removed from the slice
933 # CreateSliver has supported the options argument for a while now so it should
934 # be safe to assume this server support it
936 api_options ['append'] = False
937 api_options ['call_id'] = unique_call_id()
939 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
940 value = ReturnValue.get_value(result)
942 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
943 if options.file is not None:
944 save_rspec_to_file (value, options.file)
945 if (self.options.raw is None) and (options.file is None):
950 def delete(self, options, args):
952 delete named slice (DeleteSliver)
954 server = self.sliceapi()
958 slice_urn = hrn_to_urn(slice_hrn, 'slice')
961 slice_cred = self.slice_credential_string(slice_hrn)
964 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
965 creds.append(delegated_cred)
967 # options and call_id when supported
969 api_options ['call_id'] = unique_call_id()
970 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
971 value = ReturnValue.get_value(result)
973 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
978 def status(self, options, args):
980 retrieve slice status (SliverStatus)
982 server = self.sliceapi()
986 slice_urn = hrn_to_urn(slice_hrn, 'slice')
989 slice_cred = self.slice_credential_string(slice_hrn)
992 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
993 creds.append(delegated_cred)
995 # options and call_id when supported
997 api_options['call_id']=unique_call_id()
998 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
999 value = ReturnValue.get_value(result)
1000 if self.options.raw:
1001 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1005 def start(self, options, args):
1007 start named slice (Start)
1009 server = self.sliceapi()
1013 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1016 slice_cred = self.slice_credential_string(args[0])
1017 creds = [slice_cred]
1018 if options.delegate:
1019 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1020 creds.append(delegated_cred)
1021 # xxx Thierry - does this not need an api_options as well ?
1022 result = server.Start(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)
1030 def stop(self, options, args):
1032 stop named slice (Stop)
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)
1044 result = server.Stop(slice_urn, creds)
1045 value = ReturnValue.get_value(result)
1046 if self.options.raw:
1047 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1053 def reset(self, options, args):
1055 reset named slice (reset_slice)
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)
1067 result = server.reset_slice(creds, slice_urn)
1068 value = ReturnValue.get_value(result)
1069 if self.options.raw:
1070 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1075 def renew(self, options, args):
1077 renew slice (RenewSliver)
1079 server = self.sliceapi()
1082 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1084 slice_cred = self.slice_credential_string(args[0])
1085 creds = [slice_cred]
1086 if options.delegate:
1087 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1088 creds.append(delegated_cred)
1091 # options and call_id when supported
1093 api_options['call_id']=unique_call_id()
1094 result = server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1095 value = ReturnValue.get_value(result)
1096 if self.options.raw:
1097 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1103 def shutdown(self, options, args):
1105 shutdown named slice (Shutdown)
1107 server = self.sliceapi()
1110 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1112 slice_cred = self.slice_credential_string(slice_hrn)
1113 creds = [slice_cred]
1114 if options.delegate:
1115 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1116 creds.append(delegated_cred)
1117 result = server.Shutdown(slice_urn, creds)
1118 value = ReturnValue.get_value(result)
1119 if self.options.raw:
1120 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1126 def get_ticket(self, options, args):
1128 get a ticket for the specified slice
1130 server = self.sliceapi()
1132 slice_hrn, rspec_path = args[0], args[1]
1133 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1135 slice_cred = self.slice_credential_string(slice_hrn)
1136 creds = [slice_cred]
1137 if options.delegate:
1138 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1139 creds.append(delegated_cred)
1141 rspec_file = self.get_rspec_file(rspec_path)
1142 rspec = open(rspec_file).read()
1143 # options and call_id when supported
1145 api_options['call_id']=unique_call_id()
1146 # get ticket at the server
1147 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1149 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1150 self.logger.info("writing ticket to %s"%file)
1151 ticket = SfaTicket(string=ticket_string)
1152 ticket.save_to_file(filename=file, save_parents=True)
1154 def redeem_ticket(self, options, args):
1156 Connects to nodes in a slice and redeems a ticket
1157 (slice hrn is retrieved from the ticket)
1159 ticket_file = args[0]
1161 # get slice hrn from the ticket
1162 # use this to get the right slice credential
1163 ticket = SfaTicket(filename=ticket_file)
1165 ticket_string = ticket.save_to_string(save_parents=True)
1167 slice_hrn = ticket.gidObject.get_hrn()
1168 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1169 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1170 slice_cred = self.slice_credential_string(slice_hrn)
1172 # get a list of node hostnames from the RSpec
1173 tree = etree.parse(StringIO(ticket.rspec))
1174 root = tree.getroot()
1175 hostnames = root.xpath("./network/site/node/hostname/text()")
1177 # create an xmlrpc connection to the component manager at each of these
1178 # components and gall redeem_ticket
1180 for hostname in hostnames:
1182 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1183 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1184 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1185 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1186 timeout=self.options.timeout, verbose=self.options.debug)
1187 server.RedeemTicket(ticket_string, slice_cred)
1188 self.logger.info("Success")
1189 except socket.gaierror:
1190 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1191 except Exception, e:
1192 self.logger.log_exc(e.message)
1195 def create_gid(self, options, args):
1197 Create a GID (CreateGid)
1202 target_hrn = args[0]
1203 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.bootstrap.my_gid_string())
1205 filename = options.file
1207 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1208 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1209 GID(string=gid).save_to_file(filename)
1212 def delegate(self, options, args):
1214 (locally) create delegate credential for use by given hrn
1216 delegee_hrn = args[0]
1217 if options.delegate_user:
1218 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1219 elif options.delegate_slice:
1220 slice_cred = self.slice_credential_string(options.delegate_slice)
1221 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1223 self.logger.warning("Must specify either --user or --slice <hrn>")
1225 delegated_cred = Credential(string=cred)
1226 object_hrn = delegated_cred.get_gid_object().get_hrn()
1227 if options.delegate_user:
1228 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1229 + get_leaf(object_hrn) + ".cred")
1230 elif options.delegate_slice:
1231 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1232 + get_leaf(object_hrn) + ".cred")
1234 delegated_cred.save_to_file(dest_fn, save_parents=True)
1236 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1238 def get_trusted_certs(self, options, args):
1240 return uhe trusted certs at this interface (get_trusted_certs)
1242 trusted_certs = self.registry().get_trusted_certs()
1243 for trusted_cert in trusted_certs:
1244 gid = GID(string=trusted_cert)
1246 cert = Certificate(string=trusted_cert)
1247 self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())