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