take advantage of the new Table::dict method
[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_index = local_foreign_nodes.dict('hostname')
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         remote_fields = ['boot_state','model','version','date_created','last_updated']
124
125         ### scan the new entries, and mark them uptodate
126         for node in peer_get_nodes:
127             hostname = node['hostname']
128             foreign_id = node ['node_id']
129             try:
130                 foreign_node = local_foreign_nodes_index[hostname]
131                 if foreign_node['peer_id'] != peer_id:
132                     ### the node has changed its plc, needs to update peer_node
133                     old_peer_id = foreign_node['peer_id']
134                     old_peers=Peers(self.api,[peer_id])
135                     assert old_peer[0]
136                     # remove from previous peer
137                     old_peers[0].manage_node(foreign_node,None,False)
138                     # add to new peer
139                     self.manage_node(foreign_node,foreign_id,True)
140                     foreign_node['peer_id'] = peer_id
141                 ### update it anyway: copy other relevant fields
142                 for field in remote_fields:
143                     foreign_node[field]=node[field]
144                 # this row is now valid
145                 foreign_node.uptodate=True
146                 foreign_node.sync()
147             except:
148                 new_foreign_node = ForeignNode(self.api, {'hostname':hostname})
149                 for field in remote_fields:
150                     new_foreign_node[field]=node[field]
151                 ### need to sync so we get a node_id
152                 new_foreign_node.sync()
153                 new_foreign_node.uptodate = True
154                 self.manage_node(new_foreign_node,foreign_id,True)
155                 local_foreign_nodes_index[hostname]=new_foreign_node
156
157         ### delete entries that are not uptodate
158         for foreign_node in local_foreign_nodes:
159             if not foreign_node.uptodate:
160                 foreign_node.delete()
161
162         return len(peer_get_nodes)-old_count
163         
164     def refresh_slices (self, peer_get_slices):
165         return 0
166
167         
168 class Peers (Table):
169     """ 
170     Maps to the peers table in the database
171     """
172     
173     def __init__ (self, api, peer_filter = None, columns = None):
174         Table.__init__(self, api, Peer, columns)
175
176         sql = "SELECT %s FROM view_peers WHERE deleted IS False" % \
177               ", ".join(self.columns)
178
179         if peer_filter is not None:
180             if isinstance(peer_filter, (list, tuple, set)):
181                 # Separate the list into integers and strings
182                 ints = filter(lambda x: isinstance(x, (int, long)), peer_filter)
183                 strs = filter(lambda x: isinstance(x, StringTypes), peer_filter)
184                 peer_filter = Filter(Peer.fields, {'peer_id': ints, 'peername': strs})
185                 sql += " AND (%s)" % peer_filter.sql(api, "OR")
186             elif isinstance(peer_filter, dict):
187                 peer_filter = Filter(Peer.fields, peer_filter)
188                 sql += " AND (%s)" % peer_filter.sql(api, "AND")
189
190         self.selectall(sql)