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
17 from lxml import etree
18 from StringIO import StringIO
19 from optparse import OptionParser
20 from pprint import PrettyPrinter
22 from sfa.trust.certificate import Keypair, Certificate
23 from sfa.trust.gid import GID
24 from sfa.trust.credential import Credential
25 from sfa.trust.sfaticket import SfaTicket
27 from sfa.util.faults import SfaInvalidArgument
28 from sfa.util.sfalogging import sfi_logger
29 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
30 from sfa.util.config import Config
31 from sfa.util.version import version_core
32 from sfa.util.cache import Cache
34 from sfa.storage.record import Record
36 from sfa.rspecs.rspec import RSpec
37 from sfa.rspecs.rspec_converter import RSpecConverter
38 from sfa.rspecs.version_manager import VersionManager
40 from sfa.client.sfaclientlib import SfaClientBootstrap
41 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
42 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
43 from sfa.client.return_value import ReturnValue
47 # utility methods here
48 def optparse_listvalue_callback(option, opt, value, parser):
49 setattr(parser.values, option.dest, value.split(','))
52 def display_rspec(rspec, format='rspec'):
54 tree = etree.parse(StringIO(rspec))
56 result = root.xpath("./network/site/node/hostname/text()")
57 elif format in ['ip']:
58 # The IP address is not yet part of the new RSpec
59 # so this doesn't do anything yet.
60 tree = etree.parse(StringIO(rspec))
62 result = root.xpath("./network/site/node/ipv4/text()")
69 def display_list(results):
70 for result in results:
73 def display_records(recordList, dump=False):
74 ''' Print all fields in the record'''
75 for record in recordList:
76 display_record(record, dump)
78 def display_record(record, dump=False):
82 info = record.getdict()
83 print "%s (%s)" % (info['hrn'], info['type'])
87 def filter_records(type, records):
89 for record in records:
90 if (record['type'] == type) or (type == "all"):
91 filtered_records.append(record)
92 return filtered_records
96 def save_raw_to_file(var, filename, format="text", banner=None):
98 # if filename is "-", send it to stdout
101 f = open(filename, "w")
106 elif format == "pickled":
107 f.write(pickle.dumps(var))
108 elif format == "json":
109 if hasattr(json, "dumps"):
110 f.write(json.dumps(var)) # python 2.6
112 f.write(json.write(var)) # python 2.5
114 # this should never happen
115 print "unknown output format", format
117 f.write('\n'+banner+"\n")
119 def save_rspec_to_file(rspec, filename):
120 if not filename.endswith(".rspec"):
121 filename = filename + ".rspec"
122 f = open(filename, 'w')
127 def save_records_to_file(filename, record_dicts, format="xml"):
130 for record_dict in record_dicts:
132 save_record_to_file(filename + "." + str(index), record_dict)
134 save_record_to_file(filename, record_dict)
136 elif format == "xmllist":
137 f = open(filename, "w")
138 f.write("<recordlist>\n")
139 for record_dict in record_dicts:
140 record_obj=Record(dict=record_dict)
141 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
142 f.write("</recordlist>\n")
144 elif format == "hrnlist":
145 f = open(filename, "w")
146 for record_dict in record_dicts:
147 record_obj=Record(dict=record_dict)
148 f.write(record_obj.hrn + "\n")
151 # this should never happen
152 print "unknown output format", format
154 def save_record_to_file(filename, record_dict):
155 record = Record(dict=record_dict)
156 xml = record.save_as_xml()
157 f=codecs.open(filename, encoding='utf-8',mode="w")
162 # minimally check a key argument
163 def check_ssh_key (key):
164 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
165 return re.match(good_ssh_key, key, re.IGNORECASE)
168 def load_record_from_opts(options):
170 if hasattr(options, 'xrn') and options.xrn:
171 if hasattr(options, 'type') and options.type:
172 xrn = Xrn(options.xrn, options.type)
174 xrn = Xrn(options.xrn)
175 record_dict['urn'] = xrn.get_urn()
176 record_dict['hrn'] = xrn.get_hrn()
177 record_dict['type'] = xrn.get_type()
178 if hasattr(options, 'url') and options.url:
179 record_dict['url'] = options.url
180 if hasattr(options, 'description') and options.description:
181 record_dict['description'] = options.description
182 if hasattr(options, 'key') and options.key:
184 pubkey = open(options.key, 'r').read()
187 if not check_ssh_key (pubkey):
188 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
189 record_dict['keys'] = [pubkey]
190 if hasattr(options, 'slices') and options.slices:
191 record_dict['slices'] = options.slices
192 if hasattr(options, 'researchers') and options.researchers:
193 record_dict['researcher'] = options.researchers
194 if hasattr(options, 'email') and options.email:
195 record_dict['email'] = options.email
196 if hasattr(options, 'pis') and options.pis:
197 record_dict['pi'] = options.pis
200 if 'type' in record_dict and record_dict['type'] == 'user':
201 if not 'first_name' in record_dict:
202 record_dict['first_name'] = record_dict['hrn']
203 if 'last_name' not in record_dict:
204 record_dict['last_name'] = record_dict['hrn']
206 return Record(dict=record_dict)
208 def load_record_from_file(filename):
209 f=codecs.open(filename, encoding="utf-8", mode="r")
210 xml_string = f.read()
212 return Record(xml=xml_string)
216 def unique_call_id(): return uuid.uuid4().urn
220 # dirty hack to make this class usable from the outside
221 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
224 def default_sfi_dir ():
225 if os.path.isfile("./sfi_config"):
228 return os.path.expanduser("~/.sfi/")
230 # dummy to meet Sfi's expectations for its 'options' field
231 # i.e. s/t we can do setattr on
235 def __init__ (self,options=None):
236 if options is None: options=Sfi.DummyOptions()
237 for opt in Sfi.required_options:
238 if not hasattr(options,opt): setattr(options,opt,None)
239 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
240 self.options = options
242 self.authority = None
243 self.logger = sfi_logger
244 self.logger.enable_console()
245 self.available_names = [ tuple[0] for tuple in Sfi.available ]
246 self.available_dict = dict (Sfi.available)
248 # tuples command-name expected-args in the order in which they should appear in the help
251 ("list", "authority"),
254 ("update", "record"),
257 ("resources", "[slice_hrn]"),
258 ("create", "slice_hrn rspec"),
259 ("delete", "slice_hrn"),
260 ("status", "slice_hrn"),
261 ("start", "slice_hrn"),
262 ("stop", "slice_hrn"),
263 ("reset", "slice_hrn"),
264 ("renew", "slice_hrn time"),
265 ("shutdown", "slice_hrn"),
266 ("get_ticket", "slice_hrn rspec"),
267 ("redeem_ticket", "ticket"),
268 ("delegate", "name"),
269 ("create_gid", "[name]"),
270 ("get_trusted_certs", "cred"),
274 def print_command_help (self, options):
275 verbose=getattr(options,'verbose')
276 format3="%18s %-15s %s"
279 print format3%("command","cmd_args","description")
283 self.create_parser().print_help()
284 for command in self.available_names:
285 args=self.available_dict[command]
286 method=getattr(self,command,None)
288 if method: doc=getattr(method,'__doc__',"")
289 if not doc: doc="*** no doc found ***"
290 doc=doc.strip(" \t\n")
291 doc=doc.replace("\n","\n"+35*' ')
294 print format3%(command,args,doc)
296 self.create_command_parser(command).print_help()
298 def create_command_parser(self, command):
299 if command not in self.available_dict:
300 msg="Invalid command\n"
302 msg += ','.join(self.available_names)
303 self.logger.critical(msg)
306 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
307 % (command, self.available_dict[command]))
309 if command in ("add", "update"):
310 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
311 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
312 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
313 parser.add_option('-u', '--url', dest='url', metavar='<url>', default=None, help="URL, useful for slices")
314 parser.add_option('-d', '--description', dest='description', metavar='<description>',
315 help='Description, useful for slices', default=None)
316 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
318 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='slice xrns',
319 default='', type="str", action='callback', callback=optparse_listvalue_callback)
320 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
321 help='slice researchers', default='', type="str", action='callback',
322 callback=optparse_listvalue_callback)
323 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Principal Investigators/Project Managers',
324 default='', type="str", action='callback', callback=optparse_listvalue_callback)
325 parser.add_option('-f', '--firstname', dest='firstname', metavar='<firstname>', help='user first name')
326 parser.add_option('-l', '--lastname', dest='lastname', metavar='<firstname>', help='user last name')
328 # user specifies remote aggregate/sm/component
329 if command in ("resources", "slices", "create", "delete", "start", "stop",
330 "restart", "shutdown", "get_ticket", "renew", "status"):
331 parser.add_option("-d", "--delegate", dest="delegate", default=None,
333 help="Include a credential delegated to the user's root"+\
334 "authority in set of credentials for this call")
336 # registy filter option
337 if command in ("list", "show", "remove"):
338 parser.add_option("-t", "--type", dest="type", type="choice",
339 help="type filter ([all]|user|slice|authority|node|aggregate)",
340 choices=("all", "user", "slice", "authority", "node", "aggregate"),
342 if command in ("resources"):
344 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
345 help="schema type and version of resulting RSpec")
346 # disable/enable cached rspecs
347 parser.add_option("-c", "--current", dest="current", default=False,
349 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
351 parser.add_option("-f", "--format", dest="format", type="choice",
352 help="display format ([xml]|dns|ip)", default="xml",
353 choices=("xml", "dns", "ip"))
354 #panos: a new option to define the type of information about resources a user is interested in
355 parser.add_option("-i", "--info", dest="info",
356 help="optional component information", default=None)
359 # 'create' does return the new rspec, makes sense to save that too
360 if command in ("resources", "show", "list", "create_gid", 'create'):
361 parser.add_option("-o", "--output", dest="file",
362 help="output XML to file", metavar="FILE", default=None)
364 if command in ("show", "list"):
365 parser.add_option("-f", "--format", dest="format", type="choice",
366 help="display format ([text]|xml)", default="text",
367 choices=("text", "xml"))
369 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
370 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
371 choices=("xml", "xmllist", "hrnlist"))
372 if command == 'list':
373 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
374 help="list all child records", default=False)
375 if command in ("delegate"):
376 parser.add_option("-u", "--user",
377 action="store_true", dest="delegate_user", default=False,
378 help="delegate user credential")
379 parser.add_option("-s", "--slice", dest="delegate_slice",
380 help="delegate slice credential", metavar="HRN", default=None)
382 if command in ("version"):
383 parser.add_option("-R","--registry-version",
384 action="store_true", dest="version_registry", default=False,
385 help="probe registry version instead of sliceapi")
386 parser.add_option("-l","--local",
387 action="store_true", dest="version_local", default=False,
388 help="display version of the local client")
393 def create_parser(self):
395 # Generate command line parser
396 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
397 description="Commands: %s"%(" ".join(self.available_names)))
398 parser.add_option("-r", "--registry", dest="registry",
399 help="root registry", metavar="URL", default=None)
400 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
401 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
402 parser.add_option("-R", "--raw", dest="raw", default=None,
403 help="Save raw, unparsed server response to a file")
404 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
405 help="raw file format ([text]|pickled|json)", default="text",
406 choices=("text","pickled","json"))
407 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
408 help="text string to write before and after raw output")
409 parser.add_option("-d", "--dir", dest="sfi_dir",
410 help="config & working directory - default is %default",
411 metavar="PATH", default=Sfi.default_sfi_dir())
412 parser.add_option("-u", "--user", dest="user",
413 help="user name", metavar="HRN", default=None)
414 parser.add_option("-a", "--auth", dest="auth",
415 help="authority name", metavar="HRN", default=None)
416 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
417 help="verbose mode - cumulative")
418 parser.add_option("-D", "--debug",
419 action="store_true", dest="debug", default=False,
420 help="Debug (xml-rpc) protocol messages")
421 # would it make sense to use ~/.ssh/id_rsa as a default here ?
422 parser.add_option("-k", "--private-key",
423 action="store", dest="user_private_key", default=None,
424 help="point to the private key file to use if not yet installed in sfi_dir")
425 parser.add_option("-t", "--timeout", dest="timeout", default=None,
426 help="Amout of time to wait before timing out the request")
427 parser.add_option("-?", "--commands",
428 action="store_true", dest="command_help", default=False,
429 help="one page summary on commands & exit")
430 parser.disable_interspersed_args()
435 def print_help (self):
436 self.sfi_parser.print_help()
437 self.command_parser.print_help()
440 # Main: parse arguments and dispatch to command
442 def dispatch(self, command, command_options, command_args):
443 return getattr(self, command)(command_options, command_args)
446 self.sfi_parser = self.create_parser()
447 (options, args) = self.sfi_parser.parse_args()
448 if options.command_help:
449 self.print_command_help(options)
451 self.options = options
453 self.logger.setLevelFromOptVerbose(self.options.verbose)
456 self.logger.critical("No command given. Use -h for help.")
457 self.print_command_help(options)
461 self.command_parser = self.create_command_parser(command)
462 (command_options, command_args) = self.command_parser.parse_args(args[1:])
463 self.command_options = command_options
467 self.logger.info("Command=%s" % command)
470 self.dispatch(command, command_options, command_args)
472 self.logger.critical ("Unknown command %s"%command)
479 def read_config(self):
480 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
482 config = Config (config_file)
484 self.logger.critical("Failed to read configuration file %s"%config_file)
485 self.logger.info("Make sure to remove the export clauses and to add quotes")
486 if self.options.verbose==0:
487 self.logger.info("Re-run with -v for more details")
489 self.logger.log_exc("Could not read config file %s"%config_file)
494 if (self.options.sm is not None):
495 self.sm_url = self.options.sm
496 elif hasattr(config, "SFI_SM"):
497 self.sm_url = config.SFI_SM
499 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
503 if (self.options.registry is not None):
504 self.reg_url = self.options.registry
505 elif hasattr(config, "SFI_REGISTRY"):
506 self.reg_url = config.SFI_REGISTRY
508 self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
512 if (self.options.user is not None):
513 self.user = self.options.user
514 elif hasattr(config, "SFI_USER"):
515 self.user = config.SFI_USER
517 self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
521 if (self.options.auth is not None):
522 self.authority = self.options.auth
523 elif hasattr(config, "SFI_AUTH"):
524 self.authority = config.SFI_AUTH
526 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
529 self.config_file=config_file
533 def show_config (self):
534 print "From configuration file %s"%self.config_file
537 ('SFI_AUTH','authority'),
539 ('SFI_REGISTRY','reg_url'),
541 for (external_name, internal_name) in flags:
542 print "%s='%s'"%(external_name,getattr(self,internal_name))
545 # Get various credential and spec files
547 # Establishes limiting conventions
548 # - conflates MAs and SAs
549 # - assumes last token in slice name is unique
551 # Bootstraps credentials
552 # - bootstrap user credential from self-signed certificate
553 # - bootstrap authority credential from user credential
554 # - bootstrap slice credential from user credential
557 # init self-signed cert, user credentials and gid
558 def bootstrap (self):
559 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
560 # if -k is provided, use this to initialize private key
561 if self.options.user_private_key:
562 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
564 # trigger legacy compat code if needed
565 # the name has changed from just <leaf>.pkey to <hrn>.pkey
566 if not os.path.isfile(client_bootstrap.private_key_filename()):
567 self.logger.info ("private key not found, trying legacy name")
569 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
570 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
571 client_bootstrap.init_private_key_if_missing (legacy_private_key)
572 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
574 self.logger.log_exc("Can't find private key ")
578 client_bootstrap.bootstrap_my_gid()
579 # extract what's needed
580 self.private_key = client_bootstrap.private_key()
581 self.my_credential_string = client_bootstrap.my_credential_string ()
582 self.my_gid = client_bootstrap.my_gid ()
583 self.client_bootstrap = client_bootstrap
586 def my_authority_credential_string(self):
587 if not self.authority:
588 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
590 return self.client_bootstrap.authority_credential_string (self.authority)
592 def slice_credential_string(self, name):
593 return self.client_bootstrap.slice_credential_string (name)
595 # xxx should be supported by sfaclientbootstrap as well
596 def delegate_cred(self, object_cred, hrn, type='authority'):
597 # the gid and hrn of the object we are delegating
598 if isinstance(object_cred, str):
599 object_cred = Credential(string=object_cred)
600 object_gid = object_cred.get_gid_object()
601 object_hrn = object_gid.get_hrn()
603 if not object_cred.get_privileges().get_all_delegate():
604 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
607 # the delegating user's gid
608 caller_gidfile = self.my_gid()
610 # the gid of the user who will be delegated to
611 delegee_gid = self.client_bootstrap.gid(hrn,type)
612 delegee_hrn = delegee_gid.get_hrn()
613 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
614 return dcred.save_to_string(save_parents=True)
617 # Management of the servers
622 if not hasattr (self, 'registry_proxy'):
623 self.logger.info("Contacting Registry at: %s"%self.reg_url)
624 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
625 timeout=self.options.timeout, verbose=self.options.debug)
626 return self.registry_proxy
630 if not hasattr (self, 'sliceapi_proxy'):
631 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
632 if hasattr(self.command_options,'component') and self.command_options.component:
633 # resolve the hrn at the registry
634 node_hrn = self.command_options.component
635 records = self.registry().Resolve(node_hrn, self.my_credential_string)
636 records = filter_records('node', records)
638 self.logger.warning("No such component:%r"% opts.component)
640 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
641 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
643 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
644 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
645 self.sm_url = 'http://' + self.sm_url
646 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
647 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
648 timeout=self.options.timeout, verbose=self.options.debug)
649 return self.sliceapi_proxy
651 def get_cached_server_version(self, server):
652 # check local cache first
655 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
656 cache_key = server.url + "-version"
658 cache = Cache(cache_file)
661 self.logger.info("Local cache not found at: %s" % cache_file)
664 version = cache.get(cache_key)
667 result = server.GetVersion()
668 version= ReturnValue.get_value(result)
669 # cache version for 20 minutes
670 cache.add(cache_key, version, ttl= 60*20)
671 self.logger.info("Updating cache file %s" % cache_file)
672 cache.save_to_file(cache_file)
676 ### resurrect this temporarily so we can support V1 aggregates for a while
677 def server_supports_options_arg(self, server):
679 Returns true if server support the optional call_id arg, false otherwise.
681 server_version = self.get_cached_server_version(server)
683 # xxx need to rewrite this
684 if int(server_version.get('geni_api')) >= 2:
688 def server_supports_call_id_arg(self, server):
689 server_version = self.get_cached_server_version(server)
691 if 'sfa' in server_version and 'code_tag' in server_version:
692 code_tag = server_version['code_tag']
693 code_tag_parts = code_tag.split("-")
694 version_parts = code_tag_parts[0].split(".")
695 major, minor = version_parts[0], version_parts[1]
696 rev = code_tag_parts[1]
697 if int(major) == 1 and minor == 0 and build >= 22:
701 ### ois = options if supported
702 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
703 def ois (self, server, option_dict):
704 if self.server_supports_options_arg (server):
706 elif self.server_supports_call_id_arg (server):
707 return [ unique_call_id () ]
711 ### cis = call_id if supported - like ois
712 def cis (self, server):
713 if self.server_supports_call_id_arg (server):
714 return [ unique_call_id ]
718 ######################################## miscell utilities
719 def get_rspec_file(self, rspec):
720 if (os.path.isabs(rspec)):
723 file = os.path.join(self.options.sfi_dir, rspec)
724 if (os.path.isfile(file)):
727 self.logger.critical("No such rspec file %s"%rspec)
730 def get_record_file(self, record):
731 if (os.path.isabs(record)):
734 file = os.path.join(self.options.sfi_dir, record)
735 if (os.path.isfile(file)):
738 self.logger.critical("No such registry record file %s"%record)
742 #==========================================================================
743 # Following functions implement the commands
745 # Registry-related commands
746 #==========================================================================
748 def version(self, options, args):
750 display an SFA server version (GetVersion)
751 or version information about sfi itself
753 if options.version_local:
754 version=version_core()
756 if options.version_registry:
757 server=self.registry()
759 server = self.sliceapi()
760 result = server.GetVersion()
761 version = ReturnValue.get_value(result)
763 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
765 pprinter = PrettyPrinter(indent=4)
766 pprinter.pprint(version)
768 def list(self, options, args):
770 list entries in named authority registry (List)
777 if options.recursive:
778 opts['recursive'] = options.recursive
781 list = self.registry().List(hrn, self.my_credential_string, options)
783 raise Exception, "Not enough parameters for the 'list' command"
785 # filter on person, slice, site, node, etc.
786 # THis really should be in the self.filter_records funct def comment...
787 list = filter_records(options.type, list)
789 print "%s (%s)" % (record['hrn'], record['type'])
791 save_records_to_file(options.file, list, options.fileformat)
794 def show(self, options, args):
796 show details about named registry record (Resolve)
802 record_dicts = self.registry().Resolve(hrn, self.my_credential_string)
803 record_dicts = filter_records(options.type, record_dicts)
805 self.logger.error("No record of type %s"% options.type)
806 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
807 for record in records:
808 if (options.format == "text"): record.dump()
809 else: print record.save_as_xml()
811 save_records_to_file(options.file, record_dicts, options.fileformat)
814 def add(self, options, args):
815 "add record into registry from xml file (Register)"
816 auth_cred = self.my_authority_credential_string()
819 record_filepath = args[0]
820 rec_file = self.get_record_file(record_filepath)
821 record.update(load_record_from_file(rec_file).todict())
823 record.update(load_record_from_opts(options).todict())
827 return self.registry().Register(record, auth_cred)
829 def update(self, options, args):
830 "update record into registry from xml file (Update)"
833 record_filepath = args[0]
834 rec_file = self.get_record_file(record_filepath)
835 record_dict.update(load_record_from_file(rec_file).todict())
837 record_dict.update(load_record_from_opts(options).todict())
842 record = Record(dict=record_dict)
843 if record.type == "user":
844 if record.hrn == self.user:
845 cred = self.my_credential_string
847 cred = self.my_authority_credential_string()
848 elif record.type in ["slice"]:
850 cred = self.slice_credential_string(record.hrn)
851 except ServerException, e:
852 # XXX smbaker -- once we have better error return codes, update this
853 # to do something better than a string compare
854 if "Permission error" in e.args[0]:
855 cred = self.my_authority_credential_string()
858 elif record.type in ["authority"]:
859 cred = self.my_authority_credential_string()
860 elif record.type == 'node':
861 cred = self.my_authority_credential_string()
863 raise "unknown record type" + record.type
864 record_dict = record.todict()
865 return self.registry().Update(record_dict, cred)
867 def remove(self, options, args):
868 "remove registry record by name (Remove)"
869 auth_cred = self.my_authority_credential_string()
877 return self.registry().Remove(hrn, auth_cred, type)
879 # ==================================================================
880 # Slice-related commands
881 # ==================================================================
883 def slices(self, options, args):
884 "list instantiated slices (ListSlices) - returns urn's"
885 server = self.sliceapi()
887 creds = [self.my_credential_string]
889 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
890 creds.append(delegated_cred)
891 # options and call_id when supported
893 api_options['call_id']=unique_call_id()
894 result = server.ListSlices(creds, *self.ois(server,api_options))
895 value = ReturnValue.get_value(result)
897 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
902 # show rspec for named slice
903 def resources(self, options, args):
905 with no arg, discover available resources, (ListResources)
906 or with an slice hrn, shows currently provisioned resources
908 server = self.sliceapi()
913 creds.append(self.slice_credential_string(args[0]))
915 creds.append(self.my_credential_string)
917 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
919 # no need to check if server accepts the options argument since the options has
920 # been a required argument since v1 API
922 # always send call_id to v2 servers
923 api_options ['call_id'] = unique_call_id()
924 # ask for cached value if available
925 api_options ['cached'] = True
928 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
930 api_options['info'] = options.info
932 if options.current == True:
933 api_options['cached'] = False
935 api_options['cached'] = True
936 if options.rspec_version:
937 version_manager = VersionManager()
938 server_version = self.get_cached_server_version(server)
939 if 'sfa' in server_version:
940 # just request the version the client wants
941 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
943 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
945 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
946 result = server.ListResources (creds, api_options)
947 value = ReturnValue.get_value(result)
949 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
950 if options.file is not None:
951 save_rspec_to_file(value, options.file)
952 if (self.options.raw is None) and (options.file is None):
953 display_rspec(value, options.format)
957 def create(self, options, args):
959 create or update named slice with given rspec
961 server = self.sliceapi()
963 # xxx do we need to check usage (len(args)) ?
966 slice_urn = hrn_to_urn(slice_hrn, 'slice')
969 creds = [self.slice_credential_string(slice_hrn)]
970 delegated_cred = None
971 server_version = self.get_cached_server_version(server)
972 if server_version.get('interface') == 'slicemgr':
973 # delegate our cred to the slice manager
974 # do not delegate cred to slicemgr...not working at the moment
976 #if server_version.get('hrn'):
977 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
978 #elif server_version.get('urn'):
979 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
982 rspec_file = self.get_rspec_file(args[1])
983 rspec = open(rspec_file).read()
986 # need to pass along user keys to the aggregate.
988 # { urn: urn:publicid:IDN+emulab.net+user+alice
989 # keys: [<ssh key A>, <ssh key B>]
992 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
993 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
994 slice_record = slice_records[0]
995 user_hrns = slice_record['researcher']
996 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
997 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
999 if 'sfa' not in server_version:
1000 users = pg_users_arg(user_records)
1001 rspec = RSpec(rspec)
1002 rspec.filter({'component_manager_id': server_version['urn']})
1003 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1005 users = sfa_users_arg(user_records, slice_record)
1007 # do not append users, keys, or slice tags. Anything
1008 # not contained in this request will be removed from the slice
1010 # CreateSliver has supported the options argument for a while now so it should
1011 # be safe to assume this server support it
1013 api_options ['append'] = False
1014 api_options ['call_id'] = unique_call_id()
1015 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1016 value = ReturnValue.get_value(result)
1017 if self.options.raw:
1018 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1019 if options.file is not None:
1020 save_rspec_to_file (value, options.file)
1021 if (self.options.raw is None) and (options.file is None):
1026 def delete(self, options, args):
1028 delete named slice (DeleteSliver)
1030 server = self.sliceapi()
1034 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1037 slice_cred = self.slice_credential_string(slice_hrn)
1038 creds = [slice_cred]
1039 if options.delegate:
1040 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1041 creds.append(delegated_cred)
1043 # options and call_id when supported
1045 api_options ['call_id'] = unique_call_id()
1046 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1047 value = ReturnValue.get_value(result)
1048 if self.options.raw:
1049 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1054 def status(self, options, args):
1056 retrieve slice status (SliverStatus)
1058 server = self.sliceapi()
1062 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1065 slice_cred = self.slice_credential_string(slice_hrn)
1066 creds = [slice_cred]
1067 if options.delegate:
1068 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1069 creds.append(delegated_cred)
1071 # options and call_id when supported
1073 api_options['call_id']=unique_call_id()
1074 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1075 value = ReturnValue.get_value(result)
1076 if self.options.raw:
1077 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1081 def start(self, options, args):
1083 start named slice (Start)
1085 server = self.sliceapi()
1089 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1092 slice_cred = self.slice_credential_string(args[0])
1093 creds = [slice_cred]
1094 if options.delegate:
1095 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1096 creds.append(delegated_cred)
1097 # xxx Thierry - does this not need an api_options as well ?
1098 result = server.Start(slice_urn, creds)
1099 value = ReturnValue.get_value(result)
1100 if self.options.raw:
1101 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1106 def stop(self, options, args):
1108 stop named slice (Stop)
1110 server = self.sliceapi()
1113 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1115 slice_cred = self.slice_credential_string(args[0])
1116 creds = [slice_cred]
1117 if options.delegate:
1118 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1119 creds.append(delegated_cred)
1120 result = server.Stop(slice_urn, creds)
1121 value = ReturnValue.get_value(result)
1122 if self.options.raw:
1123 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1129 def reset(self, options, args):
1131 reset named slice (reset_slice)
1133 server = self.sliceapi()
1136 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1138 slice_cred = self.slice_credential_string(args[0])
1139 creds = [slice_cred]
1140 if options.delegate:
1141 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1142 creds.append(delegated_cred)
1143 result = server.reset_slice(creds, slice_urn)
1144 value = ReturnValue.get_value(result)
1145 if self.options.raw:
1146 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1151 def renew(self, options, args):
1153 renew slice (RenewSliver)
1155 server = self.sliceapi()
1158 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1160 slice_cred = self.slice_credential_string(args[0])
1161 creds = [slice_cred]
1162 if options.delegate:
1163 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1164 creds.append(delegated_cred)
1167 # options and call_id when supported
1169 api_options['call_id']=unique_call_id()
1170 result = server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1171 value = ReturnValue.get_value(result)
1172 if self.options.raw:
1173 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1179 def shutdown(self, options, args):
1181 shutdown named slice (Shutdown)
1183 server = self.sliceapi()
1186 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1188 slice_cred = self.slice_credential_string(slice_hrn)
1189 creds = [slice_cred]
1190 if options.delegate:
1191 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1192 creds.append(delegated_cred)
1193 result = server.Shutdown(slice_urn, creds)
1194 value = ReturnValue.get_value(result)
1195 if self.options.raw:
1196 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1202 def get_ticket(self, options, args):
1204 get a ticket for the specified slice
1206 server = self.sliceapi()
1208 slice_hrn, rspec_path = args[0], args[1]
1209 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1211 slice_cred = self.slice_credential_string(slice_hrn)
1212 creds = [slice_cred]
1213 if options.delegate:
1214 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1215 creds.append(delegated_cred)
1217 rspec_file = self.get_rspec_file(rspec_path)
1218 rspec = open(rspec_file).read()
1219 # options and call_id when supported
1221 api_options['call_id']=unique_call_id()
1222 # get ticket at the server
1223 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1225 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1226 self.logger.info("writing ticket to %s"%file)
1227 ticket = SfaTicket(string=ticket_string)
1228 ticket.save_to_file(filename=file, save_parents=True)
1230 def redeem_ticket(self, options, args):
1232 Connects to nodes in a slice and redeems a ticket
1233 (slice hrn is retrieved from the ticket)
1235 ticket_file = args[0]
1237 # get slice hrn from the ticket
1238 # use this to get the right slice credential
1239 ticket = SfaTicket(filename=ticket_file)
1241 ticket_string = ticket.save_to_string(save_parents=True)
1243 slice_hrn = ticket.gidObject.get_hrn()
1244 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1245 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1246 slice_cred = self.slice_credential_string(slice_hrn)
1248 # get a list of node hostnames from the RSpec
1249 tree = etree.parse(StringIO(ticket.rspec))
1250 root = tree.getroot()
1251 hostnames = root.xpath("./network/site/node/hostname/text()")
1253 # create an xmlrpc connection to the component manager at each of these
1254 # components and gall redeem_ticket
1256 for hostname in hostnames:
1258 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1259 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1260 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1261 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1262 timeout=self.options.timeout, verbose=self.options.debug)
1263 server.RedeemTicket(ticket_string, slice_cred)
1264 self.logger.info("Success")
1265 except socket.gaierror:
1266 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1267 except Exception, e:
1268 self.logger.log_exc(e.message)
1271 def create_gid(self, options, args):
1273 Create a GID (CreateGid)
1278 target_hrn = args[0]
1279 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1281 filename = options.file
1283 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1284 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1285 GID(string=gid).save_to_file(filename)
1288 def delegate(self, options, args):
1290 (locally) create delegate credential for use by given hrn
1292 delegee_hrn = args[0]
1293 if options.delegate_user:
1294 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1295 elif options.delegate_slice:
1296 slice_cred = self.slice_credential_string(options.delegate_slice)
1297 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1299 self.logger.warning("Must specify either --user or --slice <hrn>")
1301 delegated_cred = Credential(string=cred)
1302 object_hrn = delegated_cred.get_gid_object().get_hrn()
1303 if options.delegate_user:
1304 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1305 + get_leaf(object_hrn) + ".cred")
1306 elif options.delegate_slice:
1307 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1308 + get_leaf(object_hrn) + ".cred")
1310 delegated_cred.save_to_file(dest_fn, save_parents=True)
1312 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1314 def get_trusted_certs(self, options, args):
1316 return uhe trusted certs at this interface (get_trusted_certs)
1318 trusted_certs = self.registry().get_trusted_certs()
1319 for trusted_cert in trusted_certs:
1320 gid = GID(string=trusted_cert)
1322 cert = Certificate(string=trusted_cert)
1323 self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
1326 def config (self, options, args):
1327 "Display contents of current config"