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