- RefreshPeer & AddSliceToNodes had bugs
[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         # new to index it by hostname for searching later
82         local_foreign_nodes.hostname_index()
83         
84         ### mark entries for this peer outofdate
85         old_count=0;
86         for foreign_node in local_foreign_nodes:
87             if foreign_node['peer_id'] == peer_id:
88                 foreign_node.uptodate=False
89                 old_count += 1
90
91         ### these fields get copied through
92         ### xxx need to figure how to revert unix timestamp to db timestamp format
93         remote_fields = ['boot_state','model','version','date_created','last_updated']
94
95         
96         ### scan the new entries, and mark them uptodate
97         for node in current_peer_nodes:
98             hostname = node['hostname']
99             try:
100                 foreign_node = local_foreign_nodes.hostname_locate(hostname)
101                 if foreign_node['peer_id'] != peer_id:
102                     ### the node has changed its plc, needs to update peer_node
103                     old_peer_id = foreign_node['peer_id']
104                     old_peers=Peers(self.api,[peer_id])
105                     assert old_peer[0]
106                     old_peers[0].manage_node(foreign_node,False)
107                     self.manage_node(foreign_node,True)
108                     foreign_node['peer_id'] = peer_id
109                 ### update it anyway: copy other relevant fields
110                 for field in remote_fields:
111                     foreign_node[field]=node[field]
112                 # this row is now valid
113                 foreign_node.uptodate=True
114                 foreign_node.sync()
115             except:
116                 new_foreign_node = ForeignNode(self.api, {'hostname':hostname})
117                 for field in remote_fields:
118                     new_foreign_node[field]=node[field]
119                 ### need to sync so we get a node_id
120                 new_foreign_node.sync()
121                 new_foreign_node.uptodate = True
122                 self.manage_node(new_foreign_node,True)
123                 local_foreign_nodes.hostname_add_by(new_foreign_node)
124
125
126         ### delete entries that are not uptodate
127         for foreign_node in local_foreign_nodes:
128             if not foreign_node.uptodate:
129                 foreign_node.delete()
130
131         return len(current_peer_nodes)-old_count
132         
133         
134     def delete (self, commit=True):
135         """
136         Delete peer
137         """
138         
139         assert 'peer_id' in self
140
141         self['deleted'] = True
142         self.sync(commit)
143
144 class Peers (Table):
145     """ 
146     Maps to the peers table in the database
147     """
148     
149     def __init__ (self, api, peer_filter = None, columns = None):
150         Table.__init__(self, api, Peer, columns)
151
152         sql = "SELECT %s FROM view_peers WHERE deleted IS False" % \
153               ", ".join(self.columns)
154
155         if peer_filter is not None:
156             if isinstance(peer_filter, (list, tuple, set)):
157                 # Separate the list into integers and strings
158                 ints = filter(lambda x: isinstance(x, (int, long)), peer_filter)
159                 strs = filter(lambda x: isinstance(x, StringTypes), peer_filter)
160                 peer_filter = Filter(Peer.fields, {'peer_id': ints, 'peername': strs})
161                 sql += " AND (%s)" % peer_filter.sql(api, "OR")
162             elif isinstance(peer_filter, dict):
163                 peer_filter = Filter(Peer.fields, peer_filter)
164                 sql += " AND (%s)" % peer_filter.sql(api, "AND")
165
166         self.selectall(sql)