r2lab is migrating from a previous depl. based on omf_sfa
[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, Mixed
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.Addresses import Address, Addresses
13 from PLC.Persons import Person, Persons
14
15 class Site(Row):
16     """
17     Representation of a row in the sites table. To use, optionally
18     instantiate with a dict of values. Update as you would a
19     dict. Commit to the database with sync().
20     """
21
22     table_name = 'sites'
23     primary_key = 'site_id'
24     join_tables = ['person_site', 'site_address', 'peer_site']
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 = 32),
30         'is_public': Parameter(bool, "Publicly viewable site"),
31         'enabled': Parameter(bool, "Has been enabled"),
32         'latitude': Parameter(float, "Decimal latitude of the site", min = -90.0, max = 90.0, nullok = True),
33         'longitude': Parameter(float, "Decimal longitude of the site", min = -180.0, max = 180.0, nullok = True),
34         'url': Parameter(str, "URL of a page that describes the site", max = 254, nullok = True),
35         'date_created': Parameter(int, "Date and time when site entry was created, in seconds since UNIX epoch", ro = True),
36         'last_updated': Parameter(int, "Date and time when site entry was last updated, in seconds since UNIX epoch", ro = True),
37         'max_slices': Parameter(int, "Maximum number of slices that the site is able to create"),
38         'max_slivers': Parameter(int, "Maximum number of slivers that the site is able to create"),
39         'person_ids': Parameter([int], "List of account identifiers"),
40         'slice_ids': Parameter([int], "List of slice identifiers"),
41         'address_ids': Parameter([int], "List of address identifiers"),
42         'pcu_ids': Parameter([int], "List of PCU identifiers"),
43         'node_ids': Parameter([int], "List of site node identifiers"),
44         'peer_id': Parameter(int, "Peer to which this site belongs", nullok = True),
45         'peer_site_id': Parameter(int, "Foreign site identifier at peer", nullok = True),
46         'site_tag_ids' : Parameter ([int], "List of tags attached to this site"),
47         'ext_consortium_id': Parameter(int, "external consortium id", nullok = True)
48         }
49     related_fields = {
50         'persons': [Mixed(Parameter(int, "Person identifier"),
51                           Parameter(str, "Email address"))],
52         'addresses': [Mixed(Parameter(int, "Address identifer"),
53                             Filter(Address.fields))]
54         }
55     view_tags_name = "view_site_tags"
56     # tags are used by the Add/Get/Update methods to expose tags
57     # this is initialized here and updated by the accessors factory
58     tags = { }
59
60     def validate_name(self, name):
61         if not len(name):
62             raise PLCInvalidArgument, "Name must be specified"
63
64         return name
65
66     validate_abbreviated_name = validate_name
67
68     def validate_login_base(self, login_base):
69         if not len(login_base):
70             raise PLCInvalidArgument, "Login base must be specified"
71
72         if not set(login_base).issubset(string.lowercase + string.digits + '.'):
73             raise PLCInvalidArgument, "Login base must consist only of lowercase ASCII letters or numbers or dots"
74
75         conflicts = Sites(self.api, [login_base])
76         for site in conflicts:
77             if 'site_id' not in self or self['site_id'] != site['site_id']:
78                 raise PLCInvalidArgument, "login_base already in use"
79
80         return login_base
81
82     def validate_latitude(self, latitude):
83         if not self.has_key('longitude') or \
84            self['longitude'] is None:
85             raise PLCInvalidArgument, "Longitude must also be specified"
86
87         return latitude
88
89     def validate_longitude(self, longitude):
90         if not self.has_key('latitude') or \
91            self['latitude'] is None:
92             raise PLCInvalidArgument, "Latitude must also be specified"
93
94         return longitude
95
96     validate_date_created = Row.validate_timestamp
97     validate_last_updated = Row.validate_timestamp
98
99     add_person = Row.add_object(Person, 'person_site')
100     remove_person = Row.remove_object(Person, 'person_site')
101
102     add_address = Row.add_object(Address, 'site_address')
103     remove_address = Row.remove_object(Address, 'site_address')
104
105     def update_last_updated(self, commit = True):
106         """
107         Update last_updated field with current time
108         """
109
110         assert 'site_id' in self
111         assert self.table_name
112
113         self.api.db.do("UPDATE %s SET last_updated = CURRENT_TIMESTAMP " % (self.table_name) + \
114                        " where site_id = %d" % (self['site_id']) )
115         self.sync(commit)
116
117
118     def associate_persons(self, auth, field, value):
119         """
120         Adds persons found in value list to this site (using AddPersonToSite).
121         Deletes persons not found in value list from this site (using DeletePersonFromSite).
122         """
123
124         assert 'person_ids' in self
125         assert 'site_id' in self
126         assert isinstance(value, list)
127
128         (person_ids, emails) = self.separate_types(value)[0:2]
129
130         # Translate emails into person_ids
131         if emails:
132             persons = Persons(self.api, emails, ['person_id']).dict('person_id')
133             person_ids += persons.keys()
134
135         # Add new ids, remove stale ids
136         if self['person_ids'] != person_ids:
137             from PLC.Methods.AddPersonToSite import AddPersonToSite
138             from PLC.Methods.DeletePersonFromSite import DeletePersonFromSite
139             new_persons = set(person_ids).difference(self['person_ids'])
140             stale_persons = set(self['person_ids']).difference(person_ids)
141
142             for new_person in new_persons:
143                 AddPersonToSite.__call__(AddPersonToSite(self.api), auth, new_person, self['site_id'])
144             for stale_person in stale_persons:
145                 DeletePersonFromSite.__call__(DeletePersonFromSite(self.api), auth, stale_person, self['site_id'])
146
147     def associate_addresses(self, auth, field, value):
148         """
149         Deletes addresses_ids not found in value list (using DeleteAddress).
150         Adds address if slice_fields w/o address_id found in value list (using AddSiteAddress).
151         Update address if slice_fields w/ address_id found in value list (using UpdateAddress).
152         """
153
154         assert 'address_ids' in self
155         assert 'site_id' in self
156         assert isinstance(value, list)
157
158         (address_ids, blank, addresses) = self.separate_types(value)
159
160         for address in addresses:
161             if 'address_id' in address:
162                 address_ids.append(address['address_id'])
163
164         # Add new ids, remove stale ids
165         if self['address_ids'] != address_ids:
166             from PLC.Methods.DeleteAddress import DeleteAddress
167             stale_addresses = set(self['address_ids']).difference(address_ids)
168
169             for stale_address in stale_addresses:
170                 DeleteAddress.__call__(DeleteAddress(self.api), auth, stale_address)
171
172         if addresses:
173             from PLC.Methods.AddSiteAddress import AddSiteAddress
174             from PLC.Methods.UpdateAddress import UpdateAddress
175
176             updated_addresses = filter(lambda address: 'address_id' in address, addresses)
177             added_addresses = filter(lambda address: 'address_id' not in address, addresses)
178
179             for address in added_addresses:
180                 AddSiteAddress.__call__(AddSiteAddress(self.api), auth, self['site_id'], address)
181             for address in updated_addresses:
182                 address_id = address.pop('address_id')
183                 UpdateAddress.__call__(UpdateAddress(self.api), auth, address_id, address)
184
185     def delete(self, commit = True):
186         """
187         Delete existing site.
188         """
189
190         assert 'site_id' in self
191
192         # Delete accounts of all people at the site who are not
193         # members of at least one other non-deleted site.
194         persons = Persons(self.api, self['person_ids'])
195         for person in persons:
196             delete = True
197
198             person_sites = Sites(self.api, person['site_ids'])
199             for person_site in person_sites:
200                 if person_site['site_id'] != self['site_id']:
201                     delete = False
202                     break
203
204             if delete:
205                 person.delete(commit = False)
206
207         # Delete all site addresses
208         addresses = Addresses(self.api, self['address_ids'])
209         for address in addresses:
210             address.delete(commit = False)
211
212         # Delete all site slices
213         slices = Slices(self.api, self['slice_ids'])
214         for slice in slices:
215             slice.delete(commit = False)
216
217         # Delete all site PCUs
218         pcus = PCUs(self.api, self['pcu_ids'])
219         for pcu in pcus:
220             pcu.delete(commit = False)
221
222         # Delete all site nodes
223         nodes = Nodes(self.api, self['node_ids'])
224         for node in nodes:
225             node.delete(commit = False)
226
227         # Clean up miscellaneous join tables
228         for table in self.join_tables:
229             self.api.db.do("DELETE FROM %s WHERE site_id = %d" % \
230                            (table, self['site_id']))
231
232         # Mark as deleted
233         self['deleted'] = True
234         self.sync(commit)
235
236 class Sites(Table):
237     """
238     Representation of row(s) from the sites table in the
239     database.
240     """
241
242     def __init__(self, api, site_filter = None, columns = None):
243         Table.__init__(self, api, Site, columns)
244
245         view = "view_sites"
246         for tagname in self.tag_columns:
247             view= "%s left join %s using (%s)"%(view,Site.tagvalue_view_name(tagname),
248                                                 Site.primary_key)
249
250         sql = "SELECT %s FROM %s WHERE deleted IS False" % \
251             (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
252
253         if site_filter is not None:
254             if isinstance(site_filter, (list, tuple, set)):
255                 # Separate the list into integers and strings
256                 ints = filter(lambda x: isinstance(x, (int, long)), site_filter)
257                 strs = filter(lambda x: isinstance(x, StringTypes), site_filter)
258                 site_filter = Filter(Site.fields, {'site_id': ints, 'login_base': strs})
259                 sql += " AND (%s) %s" % site_filter.sql(api, "OR")
260             elif isinstance(site_filter, dict):
261                 allowed_fields=dict(Site.fields.items()+Site.tags.items())
262                 site_filter = Filter(allowed_fields, site_filter)
263                 sql += " AND (%s) %s" % site_filter.sql(api, "AND")
264             elif isinstance (site_filter, StringTypes):
265                 site_filter = Filter(Site.fields, {'login_base':site_filter})
266                 sql += " AND (%s) %s" % site_filter.sql(api, "AND")
267             elif isinstance (site_filter, (int, long)):
268                 site_filter = Filter(Site.fields, {'site_id':site_filter})
269                 sql += " AND (%s) %s" % site_filter.sql(api, "AND")
270             else:
271                 raise PLCInvalidArgument, "Wrong site filter %r"%site_filter
272
273         self.selectall(sql)