peer x foreign_node relationship in a separate peer_node table -- uses new db interface
[plcapi.git] / PLC / Peers.py
1 #
2 # Thierry Parmentelat - INRIA
3
4
5 import re
6
7 from types import StringTypes
8
9 from PLC.Faults import *
10 from PLC.Parameter import Parameter
11 from PLC.Filter import Filter
12 from PLC.Table import Row, Table
13
14 from PLC.ForeignNodes import ForeignNodes,ForeignNode
15
16 class Peer (Row):
17     """
18     Stores the list of peering PLCs in the peers table. 
19     See the Row class for more details
20     """
21
22     table_name = 'peers'
23     primary_key = 'peer_id'
24     fields = {
25         'peer_id' : Parameter (int, "Peer identifier"),
26         'peername' : Parameter (str, "Peer name"),
27         'peer_url' : Parameter (str, "Peer API url"),
28         'person_id' : Parameter (int, "Person_id of the account storing credentials - temporary"),
29         'node_ids' : Parameter ([int], "This peer's nodes ids")
30         }
31
32     def validate_peer_url (self, url):
33         """
34         Validate URL, checks it looks like https 
35         """
36         invalid_url = PLCInvalidArgument("Invalid URL")
37         if not re.compile ("^https://.*$").match(url) : 
38             raise invalid_url
39         return url
40
41     def manage_node (self, foreign_node, add_if_true_del_if_false=True, commit=True):
42         """
43         Add foreign node to a peer
44         """
45
46         assert 'peer_id' in self
47         assert 'node_id' in foreign_node
48
49         peer_id = self['peer_id']
50         node_id = foreign_node ['node_id']
51
52         if add_if_true_del_if_false:
53             ### ADDING
54             sql = "INSERT INTO peer_node VALUES (%d,%d)" % (peer_id,node_id)
55             self.api.db.do(sql)
56             if self['node_ids'] is None:
57                 self['node_ids']=[node_id,]
58             self['node_ids'].append(node_id)
59             ### DELETING
60         else:
61             sql = "DELETE FROM peer_node WHERE peer_id=%d AND node_id=%d" % (peer_id,node_id)
62             self.api.db.do(sql)
63             self['node_ids'].remove(node_id)
64
65         if commit:
66             self.api.db.commit()
67
68     def refresh_nodes (self, current_peer_nodes):
69         """
70         refreshes the foreign_nodes and peer_node tables
71         expected input is the current list of nodes as returned by GetNodes
72
73         returns the number of new nodes on this peer (can be negative)
74         """
75
76         peer_id = self['peer_id']
77         
78         # we get the whole table just in case 
79         # a host would have switched from one plc to the other
80         local_foreign_nodes = ForeignNodes (self.api)
81         
82         ### mark entries for this peer outofdate
83         old_count=0;
84         for foreign_node in local_foreign_nodes:
85             if foreign_node['peer_id'] == peer_id:
86                 foreign_node.uptodate=False
87                 old_count += 1
88
89         ### these fields get copied through
90         ### xxx need to figure how to revert unix timestamp to db timestamp format
91 #        remote_fields = ['boot_state','model','version','date_created','last_updated']
92         remote_fields = ['boot_state','model','version']
93         
94         ### scan the new entries, and mark them uptodate
95         for node in current_peer_nodes:
96             hostname = node['hostname']
97             try:
98                 foreign_node = ForeignNodes(self.api,{'hostname':hostname})[0]
99                 if foreign_node['peer_id'] != peer_id:
100                     ### the node has changed its plc, needs to update peer_node
101                     old_peer_id = foreign_node['peer_id']
102                     old_peers=Peers(self.api,[peer_id])
103                     assert old_peer[0]
104                     old_peers[0].manage_node(foreign_node,False)
105                     self.manage_node(foreign_node,True)
106                     foreign_node['peer_id'] = peer_id
107                 ### update it anyway: copy other relevant fields
108                 for field in remote_fields:
109                     foreign_node[field]=node[field]
110                 # this row is now valid
111                 foreign_node.uptodate=True
112                 foreign_node.sync()
113             except:
114                 new_foreign_node = ForeignNode(self.api, {'hostname':hostname})
115                 for field in remote_fields:
116                     new_foreign_node[field]=node[field]
117                 ### need to sync so we get a node_id
118                 new_foreign_node.sync()
119                 new_foreign_node.uptodate = True
120                 self.manage_node(new_foreign_node,True)
121
122
123         ### delete entries that are not uptodate
124         for foreign_node in local_foreign_nodes:
125             if not foreign_node.uptodate:
126                 foreign_node.delete()
127
128         return len(current_peer_nodes)-old_count
129         
130         
131     def delete (self, commit=True):
132         """
133         Delete peer
134         """
135         
136         assert 'peer_id' in self
137
138         self['deleted'] = True
139         self.sync(commit)
140
141 class Peers (Table):
142     """ 
143     Maps to the peers table in the database
144     """
145     
146     def __init__ (self, api, peer_filter = None, columns = None):
147         Table.__init__(self, api, Peer, columns)
148
149         sql = "SELECT %s FROM view_peers WHERE deleted IS False" % \
150               ", ".join(self.columns)
151
152         if peer_filter is not None:
153             if isinstance(peer_filter, (list, tuple, set)):
154                 # Separate the list into integers and strings
155                 ints = filter(lambda x: isinstance(x, (int, long)), peer_filter)
156                 strs = filter(lambda x: isinstance(x, StringTypes), peer_filter)
157                 peer_filter = Filter(Peer.fields, {'peer_id': ints, 'peername': strs})
158                 sql += " AND (%s)" % peer_filter.sql(api, "OR")
159             elif isinstance(peer_filter, dict):
160                 peer_filter = Filter(Peer.fields, peer_filter)
161                 sql += " AND (%s)" % peer_filter.sql(api, "AND")
162
163         self.selectall(sql)