Cache:
[plcapi.git] / PLC / Sites.py
1 from types import StringTypes
2 import string
3
4 from PLC.Faults import *
5 from PLC.Parameter import Parameter
6 from PLC.Filter import Filter
7 from PLC.Debug import profile
8 from PLC.Table import Row, Table
9 from PLC.Slices import Slice, Slices
10 from PLC.PCUs import PCU, PCUs
11 from PLC.Nodes import Node, Nodes
12 from PLC.NodeGroups import NodeGroup, NodeGroups
13 from PLC.Addresses import Address, Addresses
14 import PLC.Persons
15
16 class Site(Row):
17     """
18     Representation of a row in the sites table. To use, optionally
19     instantiate with a dict of values. Update as you would a
20     dict. Commit to the database with sync().
21     """
22
23     table_name = 'sites'
24     primary_key = 'site_id'
25     fields = {
26         'site_id': Parameter(int, "Site identifier"),
27         'name': Parameter(str, "Full site name", max = 254),
28         'abbreviated_name': Parameter(str, "Abbreviated site name", max = 50),
29         'login_base': Parameter(str, "Site slice prefix", max = 20),
30         'is_public': Parameter(bool, "Publicly viewable site"),
31         'latitude': Parameter(float, "Decimal latitude of the site", min = -90.0, max = 90.0, nullok = True),
32         'longitude': Parameter(float, "Decimal longitude of the site", min = -180.0, max = 180.0, nullok = True),
33         'url': Parameter(str, "URL of a page that describes the site", max = 254, nullok = True),
34         'date_created': Parameter(int, "Date and time when site entry was created, in seconds since UNIX epoch", ro = True),
35         'last_updated': Parameter(int, "Date and time when site entry was last updated, in seconds since UNIX epoch", ro = True),
36         'max_slices': Parameter(int, "Maximum number of slices that the site is able to create"),
37         'max_slivers': Parameter(int, "Maximum number of slivers that the site is able to create"),
38         'person_ids': Parameter([int], "List of account identifiers"),
39         'slice_ids': Parameter([int], "List of slice identifiers"),
40         'address_ids': Parameter([int], "List of address identifiers"),
41         'pcu_ids': Parameter([int], "List of PCU identifiers"),
42         'node_ids': Parameter([int], "List of site node identifiers"),
43         'peer_id': Parameter(int, "Peer at which this slice was created", nullok = True),
44         }
45
46     # for Cache
47     class_key = 'login_base'
48     foreign_fields = ['abbreviated_name', 'name', 'is_public', 'latitude', 'longitude',
49                       'url', 'max_slices', 'max_slivers',
50                       ]
51     # forget about these ones, they are read-only anyway
52     # handling them causes Cache to re-sync all over again 
53     # 'last_updated', 'date_created'
54     foreign_xrefs = []
55
56     def validate_name(self, name):
57         if not len(name):
58             raise PLCInvalidArgument, "Name must be specified"
59
60         return name
61
62     validate_abbreviated_name = validate_name
63
64     def validate_login_base(self, login_base):
65         if not len(login_base):
66             raise PLCInvalidArgument, "Login base must be specified"
67
68         if not set(login_base).issubset(string.ascii_letters.lower()):
69             raise PLCInvalidArgument, "Login base must consist only of lowercase ASCII letters"
70
71         conflicts = Sites(self.api, [login_base])
72         for site in conflicts:
73             if 'site_id' not in self or self['site_id'] != site['site_id']:
74                 raise PLCInvalidArgument, "login_base already in use"
75
76         return login_base
77
78     def validate_latitude(self, latitude):
79         if not self.has_key('longitude') or \
80            self['longitude'] is None:
81             raise PLCInvalidArgument, "Longitude must also be specified"
82
83         return latitude
84
85     def validate_longitude(self, longitude):
86         if not self.has_key('latitude') or \
87            self['latitude'] is None:
88             raise PLCInvalidArgument, "Latitude must also be specified"
89
90         return longitude
91
92     # timestamps
93     def validate_date_created (self, timestamp):
94         return self.validate_timestamp (timestamp)
95     def validate_last_updated (self, timestamp):
96         return self.validate_timestamp (timestamp)
97
98     def add_person(self, person, commit = True):
99         """
100         Add person to existing site.
101         """
102
103         assert 'site_id' in self
104         assert isinstance(person, PLC.Persons.Person)
105         assert 'person_id' in person
106
107         site_id = self['site_id']
108         person_id = person['person_id']
109
110         if person_id not in self['person_ids']:
111             assert site_id not in person['site_ids']
112
113             self.api.db.do("INSERT INTO person_site (person_id, site_id)" \
114                            " VALUES(%(person_id)d, %(site_id)d)",
115                            locals())
116
117             if commit:
118                 self.api.db.commit()
119
120             self['person_ids'].append(person_id)
121             person['site_ids'].append(site_id)
122
123     def remove_person(self, person, commit = True):
124         """
125         Remove person from existing site.
126         """
127
128         assert 'site_id' in self
129         assert isinstance(person, PLC.Persons.Person)
130         assert 'person_id' in person
131
132         site_id = self['site_id']
133         person_id = person['person_id']
134
135         if person_id in self['person_ids']:
136             assert site_id in person['site_ids']
137
138             self.api.db.do("DELETE FROM person_site" \
139                            " WHERE person_id = %(person_id)d" \
140                            " AND site_id = %(site_id)d",
141                            locals())
142
143             if commit:
144                 self.api.db.commit()
145
146             self['person_ids'].remove(person_id)
147             person['site_ids'].remove(site_id)
148
149     def add_address(self, address, commit = True):
150         """
151         Add address to existing site.
152         """
153
154         assert 'site_id' in self
155         assert isinstance(address, Address)
156         assert 'address_id' in address
157
158         site_id = self['site_id']
159         address_id = address['address_id']
160
161         if address_id not in self['address_ids']:
162             self.api.db.do("INSERT INTO site_address (address_id, site_id)" \
163                            " VALUES(%(address_id)d, %(site_id)d)",
164                            locals())
165
166             if commit:
167                 self.api.db.commit()
168
169             self['address_ids'].append(address_id)
170
171     def remove_address(self, address, commit = True):
172         """
173         Remove address from existing site.
174         """
175
176         assert 'site_id' in self
177         assert isinstance(address, Address)
178         assert 'address_id' in address
179
180         site_id = self['site_id']
181         address_id = address['address_id']
182
183         if address_id in self['address_ids']:
184             self.api.db.do("DELETE FROM site_address" \
185                            " WHERE address_id = %(address_id)d" \
186                            " AND site_id = %(site_id)d",
187                            locals())
188
189             if commit:
190                 self.api.db.commit()
191
192             self['address_ids'].remove(address_id)
193
194     def delete(self, commit = True):
195         """
196         Delete existing site.
197         """
198
199         assert 'site_id' in self
200
201         # Delete accounts of all people at the site who are not
202         # members of at least one other non-deleted site.
203         persons = PLC.Persons.Persons(self.api, self['person_ids'])
204         for person in persons:
205             delete = True
206
207             person_sites = Sites(self.api, person['site_ids'])
208             for person_site in person_sites:
209                 if person_site['site_id'] != self['site_id']:
210                     delete = False
211                     break
212
213             if delete:
214                 person.delete(commit = False)
215
216         # Delete all site addresses
217         addresses = Addresses(self.api, self['address_ids'])
218         for address in addresses:
219             address.delete(commit = False)
220
221         # Delete all site slices
222         slices = Slices(self.api, self['slice_ids'])
223         for slice in slices:
224             slice.delete(commit = False)
225
226         # Delete all site PCUs
227         pcus = PCUs(self.api, self['pcu_ids'])
228         for pcu in pcus:
229             pcu.delete(commit = False)
230
231         # Delete all site nodes
232         nodes = Nodes(self.api, self['node_ids'])
233         for node in nodes:
234             node.delete(commit = False)
235
236         # Clean up miscellaneous join tables
237         for table in ['person_site']:
238             self.api.db.do("DELETE FROM %s" \
239                            " WHERE site_id = %d" % \
240                            (table, self['site_id']), self)
241
242         # Mark as deleted
243         self['deleted'] = True
244         self.sync(commit)
245
246 class Sites(Table):
247     """
248     Representation of row(s) from the sites table in the
249     database.
250     """
251
252     def __init__(self, api, site_filter = None, columns = None):
253         Table.__init__(self, api, Site, columns)
254
255         sql = "SELECT %s FROM view_sites WHERE deleted IS False" % \
256               ", ".join(self.columns)
257
258         if site_filter is not None:
259             if isinstance(site_filter, (list, tuple, set)):
260                 # Separate the list into integers and strings
261                 ints = filter(lambda x: isinstance(x, (int, long)), site_filter)
262                 strs = filter(lambda x: isinstance(x, StringTypes), site_filter)
263                 site_filter = Filter(Site.fields, {'site_id': ints, 'login_base': strs})
264                 sql += " AND (%s)" % site_filter.sql(api, "OR")
265             elif isinstance(site_filter, dict):
266                 site_filter = Filter(Site.fields, site_filter)
267                 sql += " AND (%s)" % site_filter.sql(api, "AND")
268
269         self.selectall(sql)