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