Merge remote-tracking branch 'origin/pycurl' into planetlab-4_0-branch
[plcapi.git] / PLC / Peers.py
index 872a867..0973c3d 100644 (file)
@@ -3,17 +3,24 @@
 # 
 
 import re
 # 
 
 import re
-
 from types import StringTypes
 from types import StringTypes
+from urlparse import urlparse
 
 from PLC.Faults import *
 
 from PLC.Faults import *
-from PLC.Parameter import Parameter
+from PLC.Parameter import Parameter, Mixed
 from PLC.Filter import Filter
 from PLC.Table import Row, Table
 from PLC.Filter import Filter
 from PLC.Table import Row, Table
+import PLC.Auth
 
 
-from PLC.ForeignNodes import ForeignNodes,ForeignNode
+from PLC.Sites import Site, Sites
+from PLC.Persons import Person, Persons
+from PLC.Keys import Key, Keys
+from PLC.Nodes import Node, Nodes
+from PLC.SliceAttributeTypes import SliceAttributeType, SliceAttributeTypes
+from PLC.SliceAttributes import SliceAttribute, SliceAttributes
+from PLC.Slices import Slice, Slices
 
 
-class Peer (Row):
+class Peer(Row):
     """
     Stores the list of peering PLCs in the peers table. 
     See the Row class for more details
     """
     Stores the list of peering PLCs in the peers table. 
     See the Row class for more details
@@ -21,126 +28,188 @@ class Peer (Row):
 
     table_name = 'peers'
     primary_key = 'peer_id'
 
     table_name = 'peers'
     primary_key = 'peer_id'
+    join_tables = ['peer_site', 'peer_person', 'peer_key', 'peer_node',
+                   'peer_slice_attribute_type', 'peer_slice_attribute', 'peer_slice']
     fields = {
     fields = {
-       'peer_id' : Parameter (int, "Peer identifier"),
-       'peername' : Parameter (str, "Peer name"),
-       'peer_url' : Parameter (str, "Peer API url"),
-       'person_id' : Parameter (int, "Person_id of the account storing credentials - temporary"),
-       'node_ids' : Parameter ([int], "This peer's nodes ids")
+       'peer_id': Parameter (int, "Peer identifier"),
+       'peername': Parameter (str, "Peer name"),
+       'peer_url': Parameter (str, "Peer API URL"),
+       'key': Parameter(str, "Peer GPG public key"),
+       'cacert': Parameter(str, "Peer SSL public certificate"),
+        ### cross refs
+        'site_ids': Parameter([int], "List of sites for which this peer is authoritative"),
+        'person_ids': Parameter([int], "List of users for which this peer is authoritative"),
+        'key_ids': Parameter([int], "List of keys for which this peer is authoritative"),
+        'node_ids': Parameter([int], "List of nodes for which this peer is authoritative"),
+        'slice_ids': Parameter([int], "List of slices for which this peer is authoritative"),
        }
 
        }
 
-    def validate_peer_url (self, url):
+    def validate_peername(self, peername):
+        if not len(peername):
+            raise PLCInvalidArgument, "Peer name must be specified"
+
+        conflicts = Peers(self.api, [peername])
+        for peer in conflicts:
+            if 'peer_id' not in self or self['peer_id'] != peer['peer_id']:
+                raise PLCInvalidArgument, "Peer name already in use"
+
+        return peername
+
+    def validate_peer_url(self, url):
        """
        """
-       Validate URL, checks it looks like https 
+       Validate URL. Must be HTTPS.
        """
        """
-       invalid_url = PLCInvalidArgument("Invalid URL")
-       if not re.compile ("^https://.*$").match(url) : 
-           raise invalid_url
-       return url
-
-    def manage_node (self, foreign_node, add_if_true_del_if_false=True, commit=True):
-        """
-        Add foreign node to a peer
-        """
 
 
-        assert 'peer_id' in self
-        assert 'node_id' in foreign_node
+        (scheme, netloc, path, params, query, fragment) = urlparse(url)
+        if scheme != "https":
+            raise PLCInvalidArgument, "Peer URL scheme must be https"
 
 
-        peer_id = self['peer_id']
-        node_id = foreign_node ['node_id']
+       return url
 
 
-        if add_if_true_del_if_false:
-            ### ADDING
-            sql = "INSERT INTO peer_node VALUES (%d,%d)" % (peer_id,node_id)
-            self.api.db.do(sql)
-            if self['node_ids'] is None:
-                self['node_ids']=[node_id,]
-            self['node_ids'].append(node_id)
-            ### DELETING
-        else:
-            sql = "DELETE FROM peer_node WHERE peer_id=%d AND node_id=%d" % (peer_id,node_id)
-            self.api.db.do(sql)
-            self['node_ids'].remove(node_id)
-
-        if commit:
-            self.api.db.commit()
-
-    def refresh_nodes (self, current_peer_nodes):
-        """
-        refreshes the foreign_nodes and peer_node tables
-        expected input is the current list of nodes as returned by GetNodes
-
-        returns the number of new nodes on this peer (can be negative)
-        """
-
-        peer_id = self['peer_id']
-        
-       # we get the whole table just in case 
-       # a host would have switched from one plc to the other
-       local_foreign_nodes = ForeignNodes (self.api)
-        # new to index it by hostname for searching later
-        local_foreign_nodes.hostname_index()
-       
-       ### mark entries for this peer outofdate
-        old_count=0;
-       for foreign_node in local_foreign_nodes:
-           if foreign_node['peer_id'] == peer_id:
-               foreign_node.uptodate=False
-                old_count += 1
-
-        ### these fields get copied through
-        ### xxx need to figure how to revert unix timestamp to db timestamp format
-        remote_fields = ['boot_state','model','version','date_created','last_updated']
-
-        
-       ### scan the new entries, and mark them uptodate
-       for node in current_peer_nodes:
-           hostname = node['hostname']
-            try:
-                foreign_node = local_foreign_nodes.hostname_locate(hostname)
-                if foreign_node['peer_id'] != peer_id:
-                    ### the node has changed its plc, needs to update peer_node
-                    old_peer_id = foreign_node['peer_id']
-                    old_peers=Peers(self.api,[peer_id])
-                    assert old_peer[0]
-                    old_peers[0].manage_node(foreign_node,False)
-                    self.manage_node(foreign_node,True)
-                    foreign_node['peer_id'] = peer_id
-               ### update it anyway: copy other relevant fields
-                for field in remote_fields:
-                    foreign_node[field]=node[field]
-                # this row is now valid
-                foreign_node.uptodate=True
-                foreign_node.sync()
-           except:
-                new_foreign_node = ForeignNode(self.api, {'hostname':hostname})
-                for field in remote_fields:
-                    new_foreign_node[field]=node[field]
-                ### need to sync so we get a node_id
-                new_foreign_node.sync()
-                new_foreign_node.uptodate = True
-                self.manage_node(new_foreign_node,True)
-                local_foreign_nodes.hostname_add_by(new_foreign_node)
-
-
-       ### delete entries that are not uptodate
-        for foreign_node in local_foreign_nodes:
-            if not foreign_node.uptodate:
-                foreign_node.delete()
-
-        return len(current_peer_nodes)-old_count
-        
-        
-    def delete (self, commit=True):
+    def delete(self, commit = True):
        """
        """
-       Delete peer
+       Deletes this peer and all related entities.
        """
        """
-       
+
        assert 'peer_id' in self
 
        assert 'peer_id' in self
 
+        # Remove all related entities
+        for obj in \
+            Slices(self.api, self['slice_ids']) + \
+            Keys(self.api, self['key_ids']) + \
+            Persons(self.api, self['person_ids']) + \
+            Nodes(self.api, self['node_ids']) + \
+            Sites(self.api, self['site_ids']):
+            assert obj['peer_id'] == self['peer_id']
+            obj.delete(commit = False)
+
+        # Mark as deleted
        self['deleted'] = True
        self.sync(commit)
 
        self['deleted'] = True
        self.sync(commit)
 
+    def add_site(self, site, peer_site_id, commit = True):
+        """
+        Associate a local site entry with this peer.
+        """
+
+        add = Row.add_object(Site, 'peer_site')
+        add(self, site,
+            {'peer_id': self['peer_id'],
+             'site_id': site['site_id'],
+             'peer_site_id': peer_site_id},
+            commit = commit)
+
+    def add_person(self, person, peer_person_id, commit = True):
+        """
+        Associate a local user entry with this peer.
+        """
+
+        add = Row.add_object(Person, 'peer_person')
+        add(self, person,
+            {'peer_id': self['peer_id'],
+             'person_id': person['person_id'],
+             'peer_person_id': peer_person_id},
+            commit = commit)
+
+    def add_key(self, key, peer_key_id, commit = True):
+        """
+        Associate a local key entry with this peer.
+        """
+
+        add = Row.add_object(Key, 'peer_key')
+        add(self, key,
+            {'peer_id': self['peer_id'],
+             'key_id': key['key_id'],
+             'peer_key_id': peer_key_id},
+            commit = commit)
+
+    def add_node(self, node, peer_node_id, commit = True):
+        """
+        Associate a local node entry with this peer.
+        """
+
+        add = Row.add_object(Node, 'peer_node')
+        add(self, node,
+            {'peer_id': self['peer_id'],
+             'node_id': node['node_id'],
+             'peer_node_id': peer_node_id},
+            commit = commit)
+
+    def add_slice(self, slice, peer_slice_id, commit = True):
+        """
+        Associate a local slice entry with this peer.
+        """
+
+        add = Row.add_object(Slice, 'peer_slice')
+        add(self, slice,
+            {'peer_id': self['peer_id'],
+             'slice_id': slice['slice_id'],
+             'peer_slice_id': peer_slice_id},
+            commit = commit)
+
+    def connect(self, **kwds):
+        """
+        Connect to this peer via XML-RPC.
+        """
+
+        import xmlrpclib
+        from PLC.PyCurl import PyCurlTransport
+        self.server = xmlrpclib.ServerProxy(self['peer_url'],
+                                            PyCurlTransport(self['peer_url'], self['cacert']),
+                                            allow_none = 1, **kwds)
+
+    def add_auth(self, function, methodname, **kwds):
+        """
+        Sign the specified XML-RPC call and add an auth struct as the
+        first argument of the call.
+        """
+
+        def wrapper(*args, **kwds):
+            from PLC.GPG import gpg_sign
+            signature = gpg_sign(args,
+                                 self.api.config.PLC_ROOT_GPG_KEY,
+                                 self.api.config.PLC_ROOT_GPG_KEY_PUB,
+                                 methodname)
+
+            auth = {'AuthMethod': "gpg",
+                    'name': self.api.config.PLC_NAME,
+                    'signature': signature}
+
+            # Automagically add auth struct to every call
+            args = (auth,) + args
+
+            return function(*args)
+
+        return wrapper
+
+    def __getattr__(self, attr):
+        """
+        Returns a callable API function if attr is the name of a
+        PLCAPI function; otherwise, returns the specified attribute.
+        """
+
+        try:
+            # Figure out if the specified attribute is the name of a
+            # PLCAPI function. If so and the function requires an
+            # authentication structure as its first argument, return a
+            # callable that automagically adds an auth struct to the
+            # call.
+            methodname = attr
+            api_function = self.api.callable(methodname)
+            if api_function.accepts and \
+               (isinstance(api_function.accepts[0], PLC.Auth.Auth) or \
+                (isinstance(api_function.accepts[0], Mixed) and \
+                 filter(lambda param: isinstance(param, Auth), api_function.accepts[0]))):
+                function = getattr(self.server, methodname)
+                return self.add_auth(function, methodname)
+        except Exception, err:
+            pass
+
+        if hasattr(self, attr):
+            return getattr(self, attr)
+        else:
+            raise AttributeError, "type object 'Peer' has no attribute '%s'" % attr
+
 class Peers (Table):
     """ 
     Maps to the peers table in the database
 class Peers (Table):
     """ 
     Maps to the peers table in the database
@@ -158,9 +227,9 @@ class Peers (Table):
                 ints = filter(lambda x: isinstance(x, (int, long)), peer_filter)
                 strs = filter(lambda x: isinstance(x, StringTypes), peer_filter)
                 peer_filter = Filter(Peer.fields, {'peer_id': ints, 'peername': strs})
                 ints = filter(lambda x: isinstance(x, (int, long)), peer_filter)
                 strs = filter(lambda x: isinstance(x, StringTypes), peer_filter)
                 peer_filter = Filter(Peer.fields, {'peer_id': ints, 'peername': strs})
-                sql += " AND (%s)" % peer_filter.sql(api, "OR")
+                sql += " AND (%s) %s" % peer_filter.sql(api, "OR")
             elif isinstance(peer_filter, dict):
                 peer_filter = Filter(Peer.fields, peer_filter)
             elif isinstance(peer_filter, dict):
                 peer_filter = Filter(Peer.fields, peer_filter)
-                sql += " AND (%s)" % peer_filter.sql(api, "AND")
+                sql += " AND (%s) %s" % peer_filter.sql(api, "AND")
 
        self.selectall(sql)
 
        self.selectall(sql)