Merge branch 'master' of ssh://git.planet-lab.org/git/sfa
[sfa.git] / sfa / client / sfi.py
index 579d38e..293e5bd 100755 (executable)
@@ -10,13 +10,14 @@ import traceback
 import socket
 import random
 import datetime
 import socket
 import random
 import datetime
+import zlib
+import codecs
 from lxml import etree
 from StringIO import StringIO
 from types import StringTypes, ListType
 from optparse import OptionParser
 from lxml import etree
 from StringIO import StringIO
 from types import StringTypes, ListType
 from optparse import OptionParser
-import zlib
 
 
-from sfa.util.sfalogging import sfa_logger,sfa_logger_goes_to_console
+from sfa.util.sfalogging import sfi_logger
 from sfa.trust.certificate import Keypair, Certificate
 from sfa.trust.gid import GID
 from sfa.trust.credential import Credential
 from sfa.trust.certificate import Keypair, Certificate
 from sfa.trust.gid import GID
 from sfa.trust.credential import Credential
@@ -26,6 +27,9 @@ from sfa.util.xrn import Xrn, get_leaf, get_authority, hrn_to_urn
 import sfa.util.xmlrpcprotocol as xmlrpcprotocol
 from sfa.util.config import Config
 from sfa.util.version import version_core
 import sfa.util.xmlrpcprotocol as xmlrpcprotocol
 from sfa.util.config import Config
 from sfa.util.version import version_core
+from sfa.util.cache import Cache
+from sfa.rspecs.rspec_version import RSpecVersion
+from sfa.rspecs.pg_rspec import pg_rspec_request_version
 
 AGGREGATE_PORT=12346
 CM_PORT=12346
 
 AGGREGATE_PORT=12346
 CM_PORT=12346
@@ -106,13 +110,17 @@ def save_record_to_file(filename, record):
     else:
         record = SfaRecord(dict=record)
     str = record.save_to_string()
     else:
         record = SfaRecord(dict=record)
     str = record.save_to_string()
-    file(filename, "w").write(str)
+    f=codecs.open(filename, encoding='utf-8',mode="w")
+    f.write(str)
+    f.close()
     return
 
 
 # load methods
 def load_record_from_file(filename):
     return
 
 
 # load methods
 def load_record_from_file(filename):
-    str = file(filename, "r").read()
+    f=codecs.open(filename, encoding="utf-8", mode="r")
+    str = f.read()
+    f.close()
     record = SfaRecord(string=str)
     return record
 
     record = SfaRecord(string=str)
     return record
 
@@ -134,14 +142,17 @@ class Sfi:
         for opt in Sfi.required_options:
             if not hasattr(options,opt): setattr(options,opt,None)
         if not hasattr(options,'sfi_dir'): options.sfi_dir=os.path.expanduser("~/.sfi/")
         for opt in Sfi.required_options:
             if not hasattr(options,opt): setattr(options,opt,None)
         if not hasattr(options,'sfi_dir'): options.sfi_dir=os.path.expanduser("~/.sfi/")
+        # xxx oops, this is dangerous, sounds like ww sometimes have discrepency
+        # would be safer to remove self.sfi_dir altogether
+        self.sfi_dir = options.sfi_dir
         self.options = options
         self.slicemgr = None
         self.registry = None
         self.user = None
         self.authority = None
         self.hashrequest = False
         self.options = options
         self.slicemgr = None
         self.registry = None
         self.user = None
         self.authority = None
         self.hashrequest = False
-        sfa_logger_goes_to_console()
-        self.logger=sfa_logger()
+        self.logger = sfi_logger
+        self.logger.enable_console()
    
     def create_cmd_parser(self, command, additional_cmdargs=None):
         cmdargs = {"list": "authority",
    
     def create_cmd_parser(self, command, additional_cmdargs=None):
         cmdargs = {"list": "authority",
@@ -151,6 +162,7 @@ class Sfi:
                   "update": "record",
                   "aggregates": "[name]",
                   "registries": "[name]",
                   "update": "record",
                   "aggregates": "[name]",
                   "registries": "[name]",
+                  "create_gid": "[name]",
                   "get_gid": [],  
                   "get_trusted_certs": "cred",
                   "slices": "",
                   "get_gid": [],  
                   "get_trusted_certs": "cred",
                   "slices": "",
@@ -214,7 +226,7 @@ class Sfi:
                                 help="optional component information", default=None)
 
 
                                 help="optional component information", default=None)
 
 
-        if command in ("resources", "show", "list"):
+        if command in ("resources", "show", "list", "create_gid"):
            parser.add_option("-o", "--output", dest="file",
                             help="output XML to file", metavar="FILE", default=None)
         
            parser.add_option("-o", "--output", dest="file",
                             help="output XML to file", metavar="FILE", default=None)
         
@@ -272,13 +284,15 @@ class Sfi:
         parser.add_option("-k", "--hashrequest",
                          action="store_true", dest="hashrequest", default=False,
                          help="Create a hash of the request that will be authenticated on the server")
         parser.add_option("-k", "--hashrequest",
                          action="store_true", dest="hashrequest", default=False,
                          help="Create a hash of the request that will be authenticated on the server")
+        parser.add_option("-t", "--timeout", dest="timeout", default=None,
+                         help="Amout of time tom wait before timing out the request")
         parser.disable_interspersed_args()
 
         return parser
         
         parser.disable_interspersed_args()
 
         return parser
         
+
     def read_config(self):
     def read_config(self):
-       config_file = self.options.sfi_dir + os.sep + "sfi_config"
+       config_file = os.path.join(self.options.sfi_dir,"sfi_config")
        try:
           config = Config (config_file)
        except:
        try:
           config = Config (config_file)
        except:
@@ -344,15 +358,56 @@ class Sfi:
        self.key = Keypair(filename=key_file) 
        self.key_file = key_file
        self.cert_file = cert_file
        self.key = Keypair(filename=key_file) 
        self.key_file = key_file
        self.cert_file = cert_file
-       self.cert = GID(filename=cert_file) 
-       # Establish connection to server(s)
+       self.cert = GID(filename=cert_file)
        self.logger.info("Contacting Registry at: %s"%self.reg_url)
        self.logger.info("Contacting Registry at: %s"%self.reg_url)
-       self.registry = xmlrpcprotocol.get_server(self.reg_url, key_file, cert_file, self.options)  
+       self.registry = xmlrpcprotocol.get_server(self.reg_url, key_file, cert_file, timeout=self.options.timeout, verbose=self.options.debug)  
        self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
        self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
-       self.slicemgr = xmlrpcprotocol.get_server(self.sm_url, key_file, cert_file, self.options)
-
+       self.slicemgr = xmlrpcprotocol.get_server(self.sm_url, key_file, cert_file, timeout=self.options.timeout, verbose=self.options.debug)
        return
        return
-    
+
+    def get_cached_server_version(self, server):
+        # check local cache first
+        cache = None
+        version = None 
+        cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
+        cache_key = server.url + "-version"
+        try:
+            cache = Cache(cache_file)
+        except IOError:
+            cache = Cache()
+            self.logger.info("Local cache not found at: %s" % cache_file)
+
+        if cache:
+            version = cache.get(cache_key)
+            
+        if not version: 
+            version = server.GetVersion()
+            # cache version for 24 hours
+            cache.add(cache_key, version, ttl= 60*60*24)
+            self.logger.info("Updating cache file %s" % cache_file)
+            cache.save_to_file(cache_file)
+
+
+        return version   
+        
+
+    def server_supports_call_id_arg(self, server):
+        """
+        Returns true if server support the optional call_id arg, false otherwise. 
+        """
+        server_version = self.get_cached_server_version(server)
+        if 'sfa' in server_version:
+            code_tag = server_version['code_tag']
+            code_tag_parts = code_tag.split("-")
+            
+            version_parts = code_tag_parts[0].split(".")
+            major, minor = version_parts[0], version_parts[1]
+            rev = code_tag_parts[1]
+            if int(major) > 1:
+                if int(minor) > 0 or int(rev) > 20:
+                    return True
+        return False                
+             
     #
     # Get various credential and spec files
     #
     #
     # Get various credential and spec files
     #
@@ -394,19 +449,20 @@ class Sfi:
         cert.sign()
         self.logger.info("Writing self-signed certificate to %s"%cert_file)
         cert.save_to_file(cert_file)
         cert.sign()
         self.logger.info("Writing self-signed certificate to %s"%cert_file)
         cert.save_to_file(cert_file)
+        self.cert = cert
         # try to get registry issued cert
         try:
             self.logger.info("Getting Registry issued cert")
             self.read_config()
             # *hack.  need to set registyr before _get_gid() is called 
         # try to get registry issued cert
         try:
             self.logger.info("Getting Registry issued cert")
             self.read_config()
             # *hack.  need to set registyr before _get_gid() is called 
-            self.registry = xmlrpcprotocol.get_server(self.reg_url, key_file, cert_file, self.options)
+            self.registry = xmlrpcprotocol.get_server(self.reg_url, key_file, cert_file, timeout=self.options.timeout, verbose=self.options.debug)
             gid = self._get_gid(type='user')
             self.registry = None 
             self.logger.info("Writing certificate to %s"%cert_file)
             gid.save_to_file(cert_file)
         except:
             self.logger.info("Failed to download Registry issued cert")
             gid = self._get_gid(type='user')
             self.registry = None 
             self.logger.info("Writing certificate to %s"%cert_file)
             gid.save_to_file(cert_file)
         except:
             self.logger.info("Failed to download Registry issued cert")
+
         return cert_file
 
     def get_cached_gid(self, file):
         return cert_file
 
     def get_cached_gid(self, file):
@@ -439,18 +495,23 @@ class Sfi:
             hrn = self.user
  
         gidfile = os.path.join(self.options.sfi_dir, hrn + ".gid")
             hrn = self.user
  
         gidfile = os.path.join(self.options.sfi_dir, hrn + ".gid")
+        print gidfile
         gid = self.get_cached_gid(gidfile)
         if not gid:
             user_cred = self.get_user_cred()
             records = self.registry.Resolve(hrn, user_cred.save_to_string(save_parents=True))
         gid = self.get_cached_gid(gidfile)
         if not gid:
             user_cred = self.get_user_cred()
             records = self.registry.Resolve(hrn, user_cred.save_to_string(save_parents=True))
-            record = None
+            if not records:
+                raise RecordNotFound(args[0])
+            record = records[0]
             if type:
             if type:
+                record=None
                 for rec in records:
                 for rec in records:
-                   if type == record['type']:
+                   if type == rec['type']:
                         record = rec 
             if not record:
                 raise RecordNotFound(args[0])
                         record = rec 
             if not record:
                 raise RecordNotFound(args[0])
-            gid = GID(string=records[0]['gid'])
+            
+            gid = GID(string=record['gid'])
             self.logger.info("Writing gid to %s"%gidfile)
             gid.save_to_file(filename=gidfile)
         return gid   
             self.logger.info("Writing gid to %s"%gidfile)
             gid.save_to_file(filename=gidfile)
         return gid   
@@ -568,7 +629,7 @@ class Sfi:
         host_parts = host.split('/')
         host_parts[0] = host_parts[0] + ":" + str(port)
         url =  "http://%s" %  "/".join(host_parts)    
         host_parts = host.split('/')
         host_parts[0] = host_parts[0] + ":" + str(port)
         url =  "http://%s" %  "/".join(host_parts)    
-        return xmlrpcprotocol.get_server(url, keyfile, certfile, self.options)
+        return xmlrpcprotocol.get_server(url, keyfile, certfile, timeout=self.options.timeout, verbose=self.options.debug)
 
     # xxx opts could be retrieved in self.options
     def get_server_from_opts(self, opts):
 
     # xxx opts could be retrieved in self.options
     def get_server_from_opts(self, opts):
@@ -593,7 +654,22 @@ class Sfi:
   
     def dispatch(self, command, cmd_opts, cmd_args):
         return getattr(self, command)(cmd_opts, cmd_args)
   
     def dispatch(self, command, cmd_opts, cmd_args):
         return getattr(self, command)(cmd_opts, cmd_args)
+
+    def create_gid(self, opts, args):
+        if len(args) < 1:
+            self.print_help()
+            sys.exit(1)
+        target_hrn = args[0]
+        user_cred = self.get_user_cred().save_to_string(save_parents=True)
+        gid = self.registry.CreateGid(user_cred, target_hrn, self.cert.save_to_string())
+        if opts.file:
+            filename = opts.file
+        else:
+            filename = os.sep.join([self.sfi_dir, '%s.gid' % target_hrn])
+        self.logger.info("writing %s gid to %s" % (target_hrn, filename))
+        GID(string=gid).save_to_file(filename)
+         
+     
     # list entires in named authority registry
     def list(self, opts, args):
         if len(args)!= 1:
     # list entires in named authority registry
     def list(self, opts, args):
         if len(args)!= 1:
@@ -626,6 +702,7 @@ class Sfi:
         hrn = args[0]
         user_cred = self.get_user_cred().save_to_string(save_parents=True)
         records = self.registry.Resolve(hrn, user_cred)
         hrn = args[0]
         user_cred = self.get_user_cred().save_to_string(save_parents=True)
         records = self.registry.Resolve(hrn, user_cred)
+        print records
         records = filter_records(opts.type, records)
         if not records:
             print "No record of type", opts.type
         records = filter_records(opts.type, records)
         if not records:
             print "No record of type", opts.type
@@ -636,7 +713,7 @@ class Sfi:
                 record = SliceRecord(dict=record)
             elif record['type'] in ['node']:
                 record = NodeRecord(dict=record)
                 record = SliceRecord(dict=record)
             elif record['type'] in ['node']:
                 record = NodeRecord(dict=record)
-            elif record['type'] in ['authority', 'ma', 'sa']:
+            elif record['type'].startswith('authority'):
                 record = AuthorityRecord(dict=record)
             else:
                 record = SfaRecord(dict=record)
                 record = AuthorityRecord(dict=record)
             else:
                 record = SfaRecord(dict=record)
@@ -644,7 +721,6 @@ class Sfi:
                 record.dump()  
             else:
                 print record.save_to_string() 
                 record.dump()  
             else:
                 print record.save_to_string() 
-       
         if opts.file:
             file = opts.file
             if not file.startswith(os.sep):
         if opts.file:
             file = opts.file
             if not file.startswith(os.sep):
@@ -762,6 +838,8 @@ class Sfi:
         """ 
         trusted_certs = self.registry.get_trusted_certs()
         for trusted_cert in trusted_certs:
         """ 
         trusted_certs = self.registry.get_trusted_certs()
         for trusted_cert in trusted_certs:
+            gid = GID(string=trusted_cert)
+            gid.dump()
             cert = Certificate(string=trusted_cert)
             self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
         return 
             cert = Certificate(string=trusted_cert)
             self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
         return 
@@ -795,7 +873,6 @@ class Sfi:
     # ==================================================================
     # Slice-related commands
     # ==================================================================
     # ==================================================================
     # Slice-related commands
     # ==================================================================
-    
 
     def version(self, opts, args):
         if opts.version_local:
 
     def version(self, opts, args):
         if opts.version_local:
@@ -845,11 +922,22 @@ class Sfi:
             delegated_cred = self.delegate_cred(cred, get_authority(self.authority))
             creds.append(delegated_cred)
         if opts.rspec_version:
             delegated_cred = self.delegate_cred(cred, get_authority(self.authority))
             creds.append(delegated_cred)
         if opts.rspec_version:
-            call_options['rspec_version'] = opts.rspec_version 
+            server_version = self.get_cached_server_version(server)
+            if 'sfa' in server_version:
+                # just request the version the client wants 
+                call_options['rspec_version'] = dict(RSpecVersion(opts.rspec_version)) 
+            else:
+                # this must be a protogeni aggregate. We should request a v2 ad rspec
+                # regardless of what the client user requested 
+                call_options['rspec_version'] = dict(pg_rspec_request_version)     
         #panos add info options
         if opts.info:
             call_options['info'] = opts.info 
         #panos add info options
         if opts.info:
             call_options['info'] = opts.info 
-        result = server.ListResources(creds, call_options,unique_call_id())
+
+        call_args = [creds, call_options]
+        if self.server_supports_call_id_arg(server):
+            call_args.append(unique_call_id())
+        result = server.ListResources(*call_args)
         format = opts.format
         if opts.file is None:
             display_rspec(result, format)
         format = opts.format
         if opts.file is None:
             display_rspec(result, format)
@@ -862,6 +950,8 @@ class Sfi:
     
     # created named slice with given rspec
     def create(self, opts, args):
     
     # created named slice with given rspec
     def create(self, opts, args):
+        server = self.get_server_from_opts(opts)
+        server_version = self.get_cached_server_version(server)
         slice_hrn = args[0]
         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
         user_cred = self.get_user_cred()
         slice_hrn = args[0]
         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
         user_cred = self.get_user_cred()
@@ -873,30 +963,48 @@ class Sfi:
         rspec_file = self.get_rspec_file(args[1])
         rspec = open(rspec_file).read()
 
         rspec_file = self.get_rspec_file(args[1])
         rspec = open(rspec_file).read()
 
+        # need to pass along user keys to the aggregate.  
         # users = [
         #  { urn: urn:publicid:IDN+emulab.net+user+alice
         #    keys: [<ssh key A>, <ssh key B>] 
         #  }]
         users = []
         # users = [
         #  { urn: urn:publicid:IDN+emulab.net+user+alice
         #    keys: [<ssh key A>, <ssh key B>] 
         #  }]
         users = []
-        server = self.get_server_from_opts(opts)
-        version = server.GetVersion()
-        if 'sfa' not in version:
-            # need to pass along user keys if this request is going to a ProtoGENI aggregate 
+        all_keys = []
+        all_key_ids = []
+        slice_records = self.registry.Resolve(slice_urn, [user_cred.save_to_string(save_parents=True)])
+        if slice_records and 'researcher' in slice_records[0]:
+            slice_record = slice_records[0]
+            user_hrns = slice_record['researcher']
+            user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
+            user_records = self.registry.Resolve(user_urns, [user_cred.save_to_string(save_parents=True)])
+            for user_record in user_records:
+                if user_record['type'] != 'user':
+                    continue
+                #user = {'urn': user_cred.get_gid_caller().get_urn(),'keys': []}
+                user = {'urn': user_cred.get_gid_caller().get_urn(), #
+                        'keys': user_record['keys'],
+                        'email': user_record['email'], #  needed for MyPLC
+                        'person_id': user_record['person_id'], # needed for MyPLC
+                        'first_name': user_record['first_name'], # needed for MyPLC
+                        'last_name': user_record['last_name'], # needed for MyPLC
+                        'slice_record': slice_record, # needed for legacy refresh peer
+                        'key_ids': user_record['key_ids'] # needed for legacy refresh peer
+                } 
+                users.append(user)
+                all_keys.extend(user_record['keys'])
+                all_key_ids.extend(user_record['key_ids'])
             # ProtoGeni Aggregates will only install the keys of the user that is issuing the
             # ProtoGeni Aggregates will only install the keys of the user that is issuing the
-            # request. So we will only pass in one user that contains the keys for all
-            # users of the slice 
-            user = {'urn': user_cred.get_gid_caller().get_urn(),
-                    'keys': []}
-            slice_record = self.registry.Resolve(slice_urn, creds)
-            if slice_record and 'researchers' in slice_record:
-                user_hrns = slice_record['researchers']
-                user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns] 
-                user_records = self.registry.Resolve(user_urns, creds)
-                for user_record in user_records:
-                    if 'keys' in user_record:
-                        user['keys'].extend(user_record['keys'])
-            users.append(user)             
-        result =  server.CreateSliver(slice_urn, creds, rspec, users, unique_call_id())
+            # request. So we will add all to the current caller's list of keys
+            if 'sfa' not in server_version:
+                for user in users:
+                    if user['urn'] == user_cred.get_gid_caller().get_urn():
+                        user['keys'] = all_keys  
+
+        call_args = [slice_urn, creds, rspec, users]
+        if self.server_supports_call_id_arg(server):
+            call_args.append(unique_call_id())
+             
+        result = server.CreateSliver(*call_args)
         print result
         return result
 
         print result
         return result
 
@@ -963,8 +1071,12 @@ class Sfi:
             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
             creds.append(delegated_cred)
         server = self.get_server_from_opts(opts)
             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
             creds.append(delegated_cred)
         server = self.get_server_from_opts(opts)
-        return server.DeleteSliver(slice_urn, creds, unique_call_id())
-    
+
+        call_args = [slice_urn, creds]
+        if self.server_supports_call_id_arg(server):
+            call_args.append(unique_call_id())
+        return server.DeleteSliver(*call_args) 
+  
     # start named slice
     def start(self, opts, args):
         slice_hrn = args[0]
     # start named slice
     def start(self, opts, args):
         slice_hrn = args[0]
@@ -1011,7 +1123,11 @@ class Sfi:
             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
             creds.append(delegated_cred)
         time = args[1]
             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
             creds.append(delegated_cred)
         time = args[1]
-        return server.RenewSliver(slice_urn, creds, time, unique_call_id())
+        
+        call_args = [slice_urn, creds, time]
+        if self.server_supports_call_id_arg(server):
+            call_args.append(unique_call_id())
+        return server.RenewSliver(*call_args)
 
 
     def status(self, opts, args):
 
 
     def status(self, opts, args):
@@ -1023,7 +1139,10 @@ class Sfi:
             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
             creds.append(delegated_cred)
         server = self.get_server_from_opts(opts)
             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
             creds.append(delegated_cred)
         server = self.get_server_from_opts(opts)
-        print server.SliverStatus(slice_urn, creds, unique_call_id())
+        call_args = [slice_urn, creds]
+        if self.server_supports_call_id_arg(server):
+            call_args.append(unique_call_id())
+        print server.SliverStatus(*call_args)
 
 
     def shutdown(self, opts, args):
 
 
     def shutdown(self, opts, args):
@@ -1062,18 +1181,18 @@ class Sfi:
         (cmd_opts, cmd_args) = self.cmd_parser.parse_args(args[1:])
 
         self.set_servers()
         (cmd_opts, cmd_args) = self.cmd_parser.parse_args(args[1:])
 
         self.set_servers()
-    
         self.logger.info("Command=%s" % command)
         if command in ("resources"):
             self.logger.debug("resources cmd_opts %s" % cmd_opts.format)
         elif command in ("list", "show", "remove"):
             self.logger.debug("cmd_opts.type %s" % cmd_opts.type)
         self.logger.info("Command=%s" % command)
         if command in ("resources"):
             self.logger.debug("resources cmd_opts %s" % cmd_opts.format)
         elif command in ("list", "show", "remove"):
             self.logger.debug("cmd_opts.type %s" % cmd_opts.type)
-        self.logger.debug('cmd_args %s',cmd_args)
+        self.logger.debug('cmd_args %s' % cmd_args)
 
         try:
             self.dispatch(command, cmd_opts, cmd_args)
         except KeyError:
             self.logger.critical ("Unknown command %s"%command)
 
         try:
             self.dispatch(command, cmd_opts, cmd_args)
         except KeyError:
             self.logger.critical ("Unknown command %s"%command)
+            raise
             sys.exit(1)
     
         return
             sys.exit(1)
     
         return