Merge from HEAD. Signed off by tmack.
[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.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 = 20),
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         'ext_consortium_id': Parameter(int, "external consortium id", nullok = True)
47         }
48
49     # for Cache
50     class_key = 'login_base'
51     foreign_fields = ['abbreviated_name', 'name', 'is_public', 'latitude', 'longitude',
52                       'url', 'max_slices', 'max_slivers',
53                       ]
54     # forget about these ones, they are read-only anyway
55     # handling them causes Cache to re-sync all over again 
56     # 'last_updated', 'date_created'
57     foreign_xrefs = []
58
59     def validate_name(self, name):
60         if not len(name):
61             raise PLCInvalidArgument, "Name must be specified"
62
63         return name
64
65     validate_abbreviated_name = validate_name
66
67     def validate_login_base(self, login_base):
68         if not len(login_base):
69             raise PLCInvalidArgument, "Login base must be specified"
70
71         if not set(login_base).issubset(string.lowercase + string.digits):
72             raise PLCInvalidArgument, "Login base must consist only of lowercase ASCII letters or numbers"
73
74         conflicts = Sites(self.api, [login_base])
75         for site in conflicts:
76             if 'site_id' not in self or self['site_id'] != site['site_id']:
77                 raise PLCInvalidArgument, "login_base already in use"
78
79         return login_base
80
81     def validate_latitude(self, latitude):
82         if not self.has_key('longitude') or \
83            self['longitude'] is None:
84             raise PLCInvalidArgument, "Longitude must also be specified"
85
86         return latitude
87
88     def validate_longitude(self, longitude):
89         if not self.has_key('latitude') or \
90            self['latitude'] is None:
91             raise PLCInvalidArgument, "Latitude must also be specified"
92
93         return longitude
94
95     validate_date_created = Row.validate_timestamp
96     validate_last_updated = Row.validate_timestamp
97
98     add_person = Row.add_object(Person, 'person_site')
99     remove_person = Row.remove_object(Person, 'person_site')
100
101     add_address = Row.add_object(Address, 'site_address')
102     remove_address = Row.remove_object(Address, 'site_address')
103
104     def delete(self, commit = True):
105         """
106         Delete existing site.
107         """
108
109         assert 'site_id' in self
110
111         # Delete accounts of all people at the site who are not
112         # members of at least one other non-deleted site.
113         persons = Persons(self.api, self['person_ids'])
114         for person in persons:
115             delete = True
116
117             person_sites = Sites(self.api, person['site_ids'])
118             for person_site in person_sites:
119                 if person_site['site_id'] != self['site_id']:
120                     delete = False
121                     break
122
123             if delete:
124                 person.delete(commit = False)
125
126         # Delete all site addresses
127         addresses = Addresses(self.api, self['address_ids'])
128         for address in addresses:
129             address.delete(commit = False)
130
131         # Delete all site slices
132         slices = Slices(self.api, self['slice_ids'])
133         for slice in slices:
134             slice.delete(commit = False)
135
136         # Delete all site PCUs
137         pcus = PCUs(self.api, self['pcu_ids'])
138         for pcu in pcus:
139             pcu.delete(commit = False)
140
141         # Delete all site nodes
142         nodes = Nodes(self.api, self['node_ids'])
143         for node in nodes:
144             node.delete(commit = False)
145
146         # Clean up miscellaneous join tables
147         for table in self.join_tables:
148             self.api.db.do("DELETE FROM %s WHERE site_id = %d" % \
149                            (table, self['site_id']))
150
151         # Mark as deleted
152         self['deleted'] = True
153         self.sync(commit)
154
155 class Sites(Table):
156     """
157     Representation of row(s) from the sites table in the
158     database.
159     """
160
161     def __init__(self, api, site_filter = None, columns = None):
162         Table.__init__(self, api, Site, columns)
163
164         sql = "SELECT %s FROM view_sites WHERE deleted IS False" % \
165               ", ".join(self.columns)
166
167         if site_filter is not None:
168             if isinstance(site_filter, (list, tuple, set)):
169                 # Separate the list into integers and strings
170                 ints = filter(lambda x: isinstance(x, (int, long)), site_filter)
171                 strs = filter(lambda x: isinstance(x, StringTypes), site_filter)
172                 site_filter = Filter(Site.fields, {'site_id': ints, 'login_base': strs})
173                 sql += " AND (%s)" % site_filter.sql(api, "OR")
174             elif isinstance(site_filter, dict):
175                 site_filter = Filter(Site.fields, site_filter)
176                 sql += " AND (%s)" % site_filter.sql(api, "AND")
177
178         self.selectall(sql)