2 # Thierry Parmentelat - INRIA
6 from types import StringTypes
8 from PLC.Faults import *
9 from PLC.Parameter import Parameter
10 from PLC.Filter import Filter
11 from PLC.Table import Row, Table
13 from PLC.Nodes import Nodes,Node
14 from PLC.ForeignNodes import ForeignNodes,ForeignNode
15 from PLC.ForeignSlices import ForeignSlices,ForeignSlice
19 Stores the list of peering PLCs in the peers table.
20 See the Row class for more details
24 primary_key = 'peer_id'
26 'peer_id' : Parameter (int, "Peer identifier"),
27 'peername' : Parameter (str, "Peer name"),
28 'peer_url' : Parameter (str, "Peer API url"),
29 'person_id' : Parameter (int, "Person_id of the account storing credentials - temporary"),
30 'node_ids' : Parameter ([int], "This peer's nodes ids"),
31 'slice_ids' : Parameter ([int], "This peer's slices ids"),
34 def validate_peer_url (self, url):
36 Validate URL, checks it looks like https
38 invalid_url = PLCInvalidArgument("Invalid URL")
39 if not re.compile ("^https://.*$").match(url) :
43 def delete (self, commit=True):
48 assert 'peer_id' in self
50 # remove nodes depending on this peer
51 for foreign_node_id in self.get_foreign_nodes():
53 foreign_node = ForeignNodes(self.api,[foreign_node_id])[0]
54 foreign_node.delete(commit)
56 print "Glitch : a foreign node instance was uncleanly deleted"
59 self['deleted'] = True
62 def get_foreign_nodes (self):
64 returns a list of the foreign nodes in this peer
66 sql="SELECT node_ids FROM peer_nodes WHERE peer_id=%d"%self['peer_id']
67 node_ids = self.api.db.selectall(sql)
68 return node_ids[0]['node_ids']
70 def manage_node (self, foreign_node, add_if_true, commit=True):
72 associate/dissociate a foreign node to/from a peer
73 foreign_node is a local object that describes a remote node
75 if add_if_true is None : performs dissociation
76 otherwise: performs association
79 assert 'peer_id' in self
80 assert 'node_id' in foreign_node
82 peer_id = self['peer_id']
83 node_id = foreign_node ['node_id']
87 sql = "INSERT INTO peer_node VALUES (%d,%d)" % (peer_id,node_id)
89 if self['node_ids'] is None:
90 self['node_ids']=[node_id,]
92 self['node_ids'].append(node_id)
95 sql = "DELETE FROM peer_node WHERE peer_id=%d AND node_id=%d" % (peer_id,node_id)
97 self['node_ids'].remove(node_id)
102 def manage_slice (self, foreign_slice, add_if_true, commit=True):
104 associate/dissociate a foreign node to/from a peer
105 foreign_slice is a local object that describes a remote slice
106 alien_id is the unique id as provided by the remote peer
108 if add_if_true is None : performs dissociation
109 otherwise: performs association
112 assert 'peer_id' in self
113 assert 'slice_id' in foreign_slice
115 peer_id = self['peer_id']
116 slice_id = foreign_slice ['slice_id']
120 sql = "INSERT INTO peer_slice VALUES (%d,%d)" % (peer_id,slice_id)
122 if self['slice_ids'] is None:
123 self['slice_ids']=[slice_id,]
125 self['slice_ids'].append(slice_id)
128 sql = "DELETE FROM peer_slice WHERE peer_id=%d AND slice_id=%d" % (peer_id,slice_id)
130 self['slice_ids'].remove(slice_id)
135 def refresh_nodes (self, peer_get_nodes):
137 refreshes the foreign_nodes and peer_node tables
138 expected input is the current list of nodes as returned by GetNodes
140 returns the number of new nodes on this peer (can be negative)
143 peer_id = self['peer_id']
145 # we get the whole table just in case
146 # a host would have switched from one plc to the other
147 local_foreign_nodes = ForeignNodes (self.api)
148 # index it by hostname for searching later
149 local_foreign_nodes_index = local_foreign_nodes.dict('hostname')
151 ### mark entries for this peer outofdate
153 for foreign_node in local_foreign_nodes:
154 if foreign_node['peer_id'] == peer_id:
155 foreign_node.uptodate=False
158 ### these fields get copied through
159 remote_fields = ['boot_state','model','version','date_created','last_updated']
161 ### scan the new entries, and mark them uptodate
162 for node in peer_get_nodes:
163 hostname = node['hostname']
165 foreign_node = local_foreign_nodes_index[hostname]
166 if foreign_node['peer_id'] != peer_id:
167 ### the node has changed its plc, needs to update peer_node
168 old_peer_id = foreign_node['peer_id']
169 old_peers=Peers(self.api,[peer_id])
171 # remove from previous peer
172 old_peers[0].manage_node(foreign_node,False,False)
174 self.manage_node(foreign_node,True,True)
175 foreign_node['peer_id'] = peer_id
176 ### update it anyway: copy other relevant fields
177 for field in remote_fields:
178 foreign_node[field]=node[field]
179 # this row is now valid
180 foreign_node.uptodate=True
183 new_foreign_node = ForeignNode(self.api, {'hostname':hostname})
184 for field in remote_fields:
185 new_foreign_node[field]=node[field]
186 ### need to sync so we get a node_id
187 new_foreign_node.sync()
188 new_foreign_node.uptodate = True
189 self.manage_node(new_foreign_node,True,True)
190 local_foreign_nodes_index[hostname]=new_foreign_node
192 ### delete entries that are not uptodate
193 for foreign_node in local_foreign_nodes:
194 if not foreign_node.uptodate:
195 foreign_node.delete()
197 return len(peer_get_nodes)-old_count
199 ### transcode node_id
200 def locate_alien_node_id_in_foreign_nodes (self, peer_foreign_nodes_dict, alien_id):
202 returns a local node_id as transcoded from an alien node_id
203 only lookups our local nodes because we dont need to know about other sites
204 returns a valid local node_id, or throws an exception
206 peer_foreign_node = peer_foreign_nodes_dict[alien_id]
207 hostname = peer_foreign_node['hostname']
208 return Nodes(self.api,[hostname])[0]['node_id']
210 def refresh_slices (self, peer_get_slices, peer_foreign_nodes):
212 refreshes the foreign_slices and peer_slice tables
213 expected input is the current list of slices as returned by GetSlices
215 returns the number of new slices on this peer (can be negative)
218 peer_id = self['peer_id']
220 # we get the whole table just in case
221 # a host would have switched from one plc to the other
222 local_foreign_slices = ForeignSlices (self.api)
223 # index it by name for searching later
224 local_foreign_slices_index = local_foreign_slices.dict('name')
226 ### mark entries for this peer outofdate
228 for foreign_slice in local_foreign_slices:
229 if foreign_slice['peer_id'] == peer_id:
230 foreign_slice.uptodate=False
233 ### these fields get copied through
234 remote_fields = ['instantiation', 'url', 'description',
235 'max_nodes', 'created', 'expires']
237 ### scan the new entries, and mark them uptodate
239 for slice in peer_get_slices:
240 ### ignore system-wide slices
241 if slice['creator_person_id'] == 1:
248 foreign_slice = local_foreign_slices_index[name]
249 if foreign_slice['peer_id'] != peer_id:
250 ### the slice has changed its plc, needs to update peer_slice
251 old_peer_id = foreign_slice['peer_id']
252 old_peers=Peers(self.api,[peer_id])
254 # remove from previous peer
255 old_peers[0].manage_slice(foreign_slice,False,False)
257 self.manage_slice(foreign_slice,True,True)
258 foreign_slice['peer_id'] = peer_id
260 foreign_slice = ForeignSlice(self.api, {'name':name})
262 # foreign_slice['site_id']=1
263 ### need to sync so we get a slice_id
265 self.manage_slice(foreign_slice,True,True)
267 local_foreign_slices_index[name]=foreign_slice
270 for field in remote_fields:
271 foreign_slice[field]=slice[field]
272 # this row is now valid
273 foreign_slice.uptodate=True
278 # in slice we get a set of node_ids
279 # but these ids are RELATIVE TO THE PEER
280 # so we need to figure the local node_id for these nodes
281 # we do this through peer_foreign_nodes
283 peer_foreign_nodes_dict = {}
284 for foreign_node in peer_foreign_nodes:
285 peer_foreign_nodes_dict[foreign_node['node_id']]=foreign_node
286 updated_node_ids = []
287 for alien_node_id in slice['node_ids']:
289 local_node_id=self.locate_alien_node_id_in_foreign_nodes(peer_foreign_nodes_dict,alien_node_id)
290 updated_node_ids.append(local_node_id)
292 # this node_id is not in our scope
294 foreign_slice.update_slice_nodes (updated_node_ids)
296 ### delete entries that are not uptodate
297 for foreign_slice in local_foreign_slices:
298 if not foreign_slice.uptodate:
299 foreign_slice.delete()
301 return new_count-old_count
305 Maps to the peers table in the database
308 def __init__ (self, api, peer_filter = None, columns = None):
309 Table.__init__(self, api, Peer, columns)
311 sql = "SELECT %s FROM view_peers WHERE deleted IS False" % \
312 ", ".join(self.columns)
314 if peer_filter is not None:
315 if isinstance(peer_filter, (list, tuple, set)):
316 # Separate the list into integers and strings
317 ints = filter(lambda x: isinstance(x, (int, long)), peer_filter)
318 strs = filter(lambda x: isinstance(x, StringTypes), peer_filter)
319 peer_filter = Filter(Peer.fields, {'peer_id': ints, 'peername': strs})
320 sql += " AND (%s)" % peer_filter.sql(api, "OR")
321 elif isinstance(peer_filter, dict):
322 peer_filter = Filter(Peer.fields, peer_filter)
323 sql += " AND (%s)" % peer_filter.sql(api, "AND")