iteration 4 & last:
[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.Nodes import Nodes,Node
14 from PLC.Slices import Slices,Slice
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         'slice_ids' : Parameter ([int], "This peer's slices ids"),
31         }
32
33     def validate_peer_url (self, url):
34         """
35         Validate URL, checks it looks like https 
36         """
37         invalid_url = PLCInvalidArgument("Invalid URL")
38         if not re.compile ("^https://.*$").match(url) : 
39             raise invalid_url
40         return url
41
42     def delete (self, commit=True):
43         """
44         Delete peer
45         """
46         
47         assert 'peer_id' in self
48
49         # remove nodes depending on this peer
50         for foreign_node in Nodes (self.api, self.get_node_ids()):
51             foreign_node.delete(commit)
52
53         # remove the peer
54         self['deleted'] = True
55         self.sync(commit)
56
57     def get_node_ids (self):
58         """
59         returns a list of the node ids in this peer
60         """
61         sql="SELECT node_ids FROM peer_nodes WHERE peer_id=%d"%self['peer_id']
62         node_ids = self.api.db.selectall(sql)
63         return node_ids[0]['node_ids']
64
65     def refresh_nodes (self, peer_get_nodes):
66         """
67         refreshes the foreign_nodes and peer_node tables
68         expected input is the current list of nodes as returned by GetNodes
69
70         returns the number of new nodes on this peer (can be negative)
71         """
72
73         peer_id = self['peer_id']
74         
75         # we get the whole table just in case 
76         # a host would have switched from one plc to the other
77         local_foreign_nodes = Nodes (self.api,None,None,'foreign')
78         
79         # index it by hostname for searching later
80         #local_foreign_nodes_index=local_foreign_nodes.dict('hostname')
81         local_foreign_nodes_index={}
82         for node in local_foreign_nodes:
83             local_foreign_nodes_index[node['hostname']]=node
84         
85         ### mark entries for this peer outofdate
86         old_count=0;
87         for foreign_node in local_foreign_nodes:
88             if foreign_node['peer_id'] == peer_id:
89                 foreign_node.uptodate=False
90                 old_count += 1
91
92         ### these fields get copied through
93         remote_fields = ['boot_state','model','version','date_created','last_updated']
94
95         ### scan the new entries, and mark them uptodate
96         for node in peer_get_nodes:
97             hostname = node['hostname']
98             try:
99                 foreign_node = local_foreign_nodes_index[hostname]
100                 if foreign_node['peer_id'] != peer_id:
101 #                    ### the node has changed its plc
102                     foreign_node['peer_id'] = peer_id
103                 ### update it anyway: copy other relevant fields
104                 for field in remote_fields:
105                     foreign_node[field]=node[field]
106                 # this row is now valid
107                 foreign_node.uptodate=True
108                 foreign_node.sync()
109             except:
110                 new_foreign_node = Node(self.api, {'hostname':hostname})
111                 new_foreign_node['peer_id']=peer_id
112                 for field in remote_fields:
113                     new_foreign_node[field]=node[field]
114                 ### need to sync so we get a node_id
115                 new_foreign_node.sync()
116                 new_foreign_node.uptodate = True
117 #                self.manage_node(new_foreign_node,True,True)
118                 local_foreign_nodes_index[hostname]=new_foreign_node
119
120         ### delete entries that are not uptodate
121         for foreign_node in local_foreign_nodes:
122             if not foreign_node.uptodate:
123                 foreign_node.delete()
124
125         return len(peer_get_nodes)-old_count
126         
127     ### transcode node_id
128     def locate_alien_node_id_in_foreign_nodes (self, peer_foreign_nodes_dict, alien_id):
129         """
130         returns a local node_id as transcoded from an alien node_id
131         only lookups our local nodes because we dont need to know about other sites
132         returns a valid local node_id, or throws an exception
133         """
134         peer_foreign_node = peer_foreign_nodes_dict[alien_id]
135         hostname = peer_foreign_node['hostname']
136         return Nodes(self.api,[hostname])[0]['node_id']
137
138     def refresh_slices (self, peer_get_slices, peer_foreign_nodes):
139         """
140         refreshes the foreign_slices and peer_slice tables
141         expected input is the current list of slices as returned by GetSlices
142
143         returns the number of new slices on this peer (can be negative)
144         """
145
146         peer_id = self['peer_id']
147         
148         # we get the whole table just in case 
149         # a host would have switched from one plc to the other
150         local_foreign_slices = Slices (self.api,{'~peer_id':None})
151         # index it by name for searching later
152         local_foreign_slices_index = local_foreign_slices.dict('name')
153         
154         ### mark entries for this peer outofdate
155         old_count=0;
156         for foreign_slice in local_foreign_slices:
157             if foreign_slice['peer_id'] == peer_id:
158                 foreign_slice.uptodate=False
159                 old_count += 1
160
161         ### these fields get copied through
162         remote_fields = ['instantiation', 'url', 'description',
163                          'max_nodes', 'created', 'expires']
164
165         ### scan the new entries, and mark them uptodate
166         new_count=0
167         for slice in peer_get_slices:
168
169             ### ignore system-wide slices
170             if slice['creator_person_id'] == 1:
171                 continue
172
173             name = slice['name']
174
175             # create or update 
176             try:
177                 foreign_slice = local_foreign_slices_index[name]
178                 if foreign_slice['peer_id'] != peer_id:
179                     # more suspucious ? - the slice moved on another peer
180                     foreign_slice['peer_id'] = peer_id;
181             except:
182                 foreign_slice = Slice(self.api, {'name':name})
183                 foreign_slice['peer_id']=self['peer_id']
184 #                ### xxx temporary 
185 #                foreign_slice['site_id']=1
186                 ### need to sync so we get a slice_id
187                 foreign_slice.sync()
188                 # insert in index
189                 local_foreign_slices_index[name]=foreign_slice
190
191             # go on with update
192             for field in remote_fields:
193                 foreign_slice[field]=slice[field]
194             # this row is now valid
195             foreign_slice.uptodate=True
196             new_count += 1
197             foreign_slice.sync()
198
199             ### handle node_ids
200             # in slice we get a set of node_ids
201             # but these ids are RELATIVE TO THE PEER
202             # so we need to figure the local node_id for these nodes
203             # we do this through peer_foreign_nodes 
204             # dictify once
205             peer_foreign_nodes_dict = {}
206             for foreign_node in peer_foreign_nodes:
207                 peer_foreign_nodes_dict[foreign_node['node_id']]=foreign_node
208             updated_node_ids = []
209             for alien_node_id in slice['node_ids']:
210                 try:
211                     local_node_id=self.locate_alien_node_id_in_foreign_nodes(peer_foreign_nodes_dict,
212                                                                              alien_node_id)
213                     updated_node_ids.append(local_node_id)
214                 except:
215                     # this node_id is not in our scope
216                     pass
217             foreign_slice.update_slice_nodes (updated_node_ids)
218
219         ### delete entries that are not uptodate
220         for foreign_slice in local_foreign_slices:
221             if not foreign_slice.uptodate:
222                 foreign_slice.delete()
223
224         return new_count-old_count
225
226 class Peers (Table):
227     """ 
228     Maps to the peers table in the database
229     """
230     
231     def __init__ (self, api, peer_filter = None, columns = None):
232         Table.__init__(self, api, Peer, columns)
233
234         sql = "SELECT %s FROM view_peers WHERE deleted IS False" % \
235               ", ".join(self.columns)
236
237         if peer_filter is not None:
238             if isinstance(peer_filter, (list, tuple, set)):
239                 # Separate the list into integers and strings
240                 ints = filter(lambda x: isinstance(x, (int, long)), peer_filter)
241                 strs = filter(lambda x: isinstance(x, StringTypes), peer_filter)
242                 peer_filter = Filter(Peer.fields, {'peer_id': ints, 'peername': strs})
243                 sql += " AND (%s)" % peer_filter.sql(api, "OR")
244             elif isinstance(peer_filter, dict):
245                 peer_filter = Filter(Peer.fields, peer_filter)
246                 sql += " AND (%s)" % peer_filter.sql(api, "AND")
247
248         self.selectall(sql)