remove PLC.Debug.log, use PLC.Logger.logger instead
[plcapi.git] / PLC / Peers.py
1 #
2 # Thierry Parmentelat - INRIA
3 #
4
5 import re
6 from types import StringTypes
7 import traceback
8 from urlparse import urlparse
9
10 import PLC.Auth
11 from PLC.Logger import logger
12 from PLC.Faults import *
13 from PLC.Namespace import hostname_to_hrn
14 from PLC.Parameter import Parameter, Mixed
15 from PLC.Filter import Filter
16 from PLC.Table import Row, Table
17 from PLC.Sites import Site, Sites
18 from PLC.Persons import Person, Persons
19 from PLC.Keys import Key, Keys
20 from PLC.Nodes import Node, Nodes
21 from PLC.TagTypes import TagType, TagTypes
22 from PLC.NodeTags import NodeTag, NodeTags
23 from PLC.SliceTags import SliceTag, SliceTags
24 from PLC.Slices import Slice, Slices
25
26 class Peer(Row):
27     """
28     Stores the list of peering PLCs in the peers table.
29     See the Row class for more details
30     """
31
32     table_name = 'peers'
33     primary_key = 'peer_id'
34     join_tables = ['peer_site', 'peer_person', 'peer_key', 'peer_node', 'peer_slice']
35     fields = {
36         'peer_id': Parameter (int, "Peer identifier"),
37         'peername': Parameter (str, "Peer name"),
38         'peer_url': Parameter (str, "Peer API URL"),
39         'key': Parameter(str, "Peer GPG public key"),
40         'cacert': Parameter(str, "Peer SSL public certificate"),
41         'shortname' : Parameter(str, "Peer short name"),
42         'hrn_root' : Parameter(str, "Root of this peer in a hierarchical naming space"),
43         ### cross refs
44         'site_ids': Parameter([int], "List of sites for which this peer is authoritative"),
45         'person_ids': Parameter([int], "List of users for which this peer is authoritative"),
46         'key_ids': Parameter([int], "List of keys for which this peer is authoritative"),
47         'node_ids': Parameter([int], "List of nodes for which this peer is authoritative"),
48         'slice_ids': Parameter([int], "List of slices for which this peer is authoritative"),
49         }
50
51     def validate_peername(self, peername):
52         if not len(peername):
53             raise PLCInvalidArgument, "Peer name must be specified"
54
55         conflicts = Peers(self.api, [peername])
56         for peer in conflicts:
57             if 'peer_id' not in self or self['peer_id'] != peer['peer_id']:
58                 raise PLCInvalidArgument, "Peer name already in use"
59
60         return peername
61
62     def validate_peer_url(self, url):
63         """
64         Validate URL. Must be HTTPS.
65         """
66
67         (scheme, netloc, path, params, query, fragment) = urlparse(url)
68         if scheme != "https":
69             raise PLCInvalidArgument, "Peer URL scheme must be https"
70         if path[-1] != '/':
71             raise PLCInvalidArgument, "Peer URL should end with /"
72
73         return url
74
75     def delete(self, commit = True):
76         """
77         Deletes this peer and all related entities.
78         """
79
80         assert 'peer_id' in self
81
82         # Remove all related entities
83         for obj in \
84             Slices(self.api, self['slice_ids']) + \
85             Keys(self.api, self['key_ids']) + \
86             Persons(self.api, self['person_ids']) + \
87             Nodes(self.api, self['node_ids']) + \
88             Sites(self.api, self['site_ids']):
89             assert obj['peer_id'] == self['peer_id']
90             obj.delete(commit = False)
91
92         # Mark as deleted
93         self['deleted'] = True
94         self.sync(commit)
95
96     def add_site(self, site, peer_site_id, commit = True):
97         """
98         Associate a local site entry with this peer.
99         """
100
101         add = Row.add_object(Site, 'peer_site')
102         add(self, site,
103             {'peer_id': self['peer_id'],
104              'site_id': site['site_id'],
105              'peer_site_id': peer_site_id},
106             commit = commit)
107
108     def remove_site(self, site, commit = True):
109         """
110         Unassociate a site with this peer.
111         """
112
113         remove = Row.remove_object(Site, 'peer_site')
114         remove(self, site, commit)
115
116     def add_person(self, person, peer_person_id, commit = True):
117         """
118         Associate a local user entry with this peer.
119         """
120
121         add = Row.add_object(Person, 'peer_person')
122         add(self, person,
123             {'peer_id': self['peer_id'],
124              'person_id': person['person_id'],
125              'peer_person_id': peer_person_id},
126             commit = commit)
127
128     def remove_person(self, person, commit = True):
129         """
130         Unassociate a site with this peer.
131         """
132
133         remove = Row.remove_object(Person, 'peer_person')
134         remove(self, person, commit)
135
136     def add_key(self, key, peer_key_id, commit = True):
137         """
138         Associate a local key entry with this peer.
139         """
140
141         add = Row.add_object(Key, 'peer_key')
142         add(self, key,
143             {'peer_id': self['peer_id'],
144              'key_id': key['key_id'],
145              'peer_key_id': peer_key_id},
146             commit = commit)
147
148     def remove_key(self, key, commit = True):
149         """
150         Unassociate a key with this peer.
151         """
152
153         remove = Row.remove_object(Key, 'peer_key')
154         remove(self, key, commit)
155
156     def add_node(self, node, peer_node_id, commit = True):
157         """
158         Associate a local node entry with this peer.
159         """
160
161         add = Row.add_object(Node, 'peer_node')
162         add(self, node,
163             {'peer_id': self['peer_id'],
164              'node_id': node['node_id'],
165              'peer_node_id': peer_node_id},
166             commit = commit)
167
168         sites = Sites(self.api, node['site_id'], ['login_base'])
169         site = sites[0]
170         login_base = site['login_base']
171         try:
172             # attempt to manually update the 'hrn' tag with the remote prefix
173             hrn_root = self['hrn_root']
174             hrn = hostname_to_hrn(hrn_root, login_base, node['hostname'])
175             tags = {'hrn': hrn}
176             Node(self.api, node).update_tags(tags)
177         except:
178             logger.exception("Could not find out hrn on hostname=%s"%node['hostname'])
179
180     def remove_node(self, node, commit = True):
181         """
182         Unassociate a node with this peer.
183         """
184
185         remove = Row.remove_object(Node, 'peer_node')
186         remove(self, node, commit)
187         # attempt to manually update the 'hrn' tag now that the node is local
188         root_auth = self.api.config.PLC_HRN_ROOT
189         sites = Sites(self.api, node['site_id'], ['login_base'])
190         site = sites[0]
191         login_base = site['login_base']
192         hrn = hostname_to_hrn(root_auth, login_base, node['hostname'])
193         tags = {'hrn': hrn}
194         Node(self.api, node).update_tags(tags)
195
196     def add_slice(self, slice, peer_slice_id, commit = True):
197         """
198         Associate a local slice entry with this peer.
199         """
200
201         add = Row.add_object(Slice, 'peer_slice')
202         add(self, slice,
203             {'peer_id': self['peer_id'],
204              'slice_id': slice['slice_id'],
205              'peer_slice_id': peer_slice_id},
206             commit = commit)
207
208     def remove_slice(self, slice, commit = True):
209         """
210         Unassociate a slice with this peer.
211         """
212
213         remove = Row.remove_object(Slice, 'peer_slice')
214         remove(self, slice, commit)
215
216     def connect(self, **kwds):
217         """
218         Connect to this peer via XML-RPC.
219         """
220
221         import xmlrpclib
222         from PLC.PyCurl import PyCurlTransport
223         self.server = xmlrpclib.ServerProxy(self['peer_url'],
224                                             PyCurlTransport(self['peer_url'], self['cacert']),
225                                             allow_none = 1, **kwds)
226
227     def add_auth(self, function, methodname, **kwds):
228         """
229         Sign the specified XML-RPC call and add an auth struct as the
230         first argument of the call.
231         """
232
233         def wrapper(*args, **kwds):
234             from PLC.GPG import gpg_sign
235             signature = gpg_sign(args,
236                                  self.api.config.PLC_ROOT_GPG_KEY,
237                                  self.api.config.PLC_ROOT_GPG_KEY_PUB,
238                                  methodname)
239
240             auth = {'AuthMethod': "gpg",
241                     'name': self.api.config.PLC_NAME,
242                     'signature': signature}
243
244             # Automagically add auth struct to every call
245             args = (auth,) + args
246
247             return function(*args)
248
249         return wrapper
250
251     def __getattr__(self, attr):
252         """
253         Returns a callable API function if attr is the name of a
254         PLCAPI function; otherwise, returns the specified attribute.
255         """
256
257         try:
258             # Figure out if the specified attribute is the name of a
259             # PLCAPI function. If so and the function requires an
260             # authentication structure as its first argument, return a
261             # callable that automagically adds an auth struct to the
262             # call.
263             methodname = attr
264             api_function = self.api.callable(methodname)
265             if api_function.accepts and \
266                (isinstance(api_function.accepts[0], PLC.Auth.Auth) or \
267                 (isinstance(api_function.accepts[0], Mixed) and \
268                  filter(lambda param: isinstance(param, Auth), api_function.accepts[0]))):
269                 function = getattr(self.server, methodname)
270                 return self.add_auth(function, methodname)
271         except Exception, err:
272             pass
273
274         if hasattr(self, attr):
275             return getattr(self, attr)
276         else:
277             raise AttributeError, "type object 'Peer' has no attribute '%s'" % attr
278
279 class Peers (Table):
280     """
281     Maps to the peers table in the database
282     """
283
284     def __init__ (self, api, peer_filter = None, columns = None):
285         Table.__init__(self, api, Peer, columns)
286
287         sql = "SELECT %s FROM view_peers WHERE deleted IS False" % \
288               ", ".join(self.columns)
289
290         if peer_filter is not None:
291             if isinstance(peer_filter, (list, tuple, set)):
292                 # Separate the list into integers and strings
293                 ints = filter(lambda x: isinstance(x, (int, long)), peer_filter)
294                 strs = filter(lambda x: isinstance(x, StringTypes), peer_filter)
295                 peer_filter = Filter(Peer.fields, {'peer_id': ints, 'peername': strs})
296                 sql += " AND (%s) %s" % peer_filter.sql(api, "OR")
297             elif isinstance(peer_filter, dict):
298                 peer_filter = Filter(Peer.fields, peer_filter)
299                 sql += " AND (%s) %s" % peer_filter.sql(api, "AND")
300             elif isinstance(peer_filter, (int, long)):
301                 peer_filter = Filter(Peer.fields, {'peer_id': peer_filter})
302                 sql += " AND (%s) %s" % peer_filter.sql(api, "AND")
303             elif isinstance(peer_filter, StringTypes):
304                 peer_filter = Filter(Peer.fields, {'peername': peer_filter})
305                 sql += " AND (%s) %s" % peer_filter.sql(api, "AND")
306             else:
307                 raise PLCInvalidArgument, "Wrong peer filter %r"%peer_filter
308
309         self.selectall(sql)