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