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