981a598a14524cc2df8a367bdbf92c634ea9b6f2
[plcapi.git] / PLC / Sites.py
1 from types import StringTypes
2 from datetime import datetime
3 import string
4
5 from PLC.Faults import *
6 from PLC.Logger import logger
7 from PLC.Parameter import Parameter, Mixed
8 from PLC.Storage.AlchemyObject import AlchemyObj
9 from PLC.Slices import Slice, Slices
10 from PLC.Persons import Person, Persons
11 from PLC.SitePersons import SitePerson, SitePersons
12 from PLC.SiteAddresses import SiteAddress, SiteAddress
13 from PLC.PCUs import PCU, PCUs
14 from PLC.Nodes import Node, Nodes
15 from PLC.Roles import Role, Roles
16 from PLC.SiteTags import SiteTag, SiteTags
17
18 class Site(AlchemyObj):
19     """
20     Representation of a row in the sites table. To use, optionally
21     instantiate with a dict of values. Update as you would a
22     dict. Commit to the database with sync().
23     """
24
25     tablename = 'sites'
26
27     fields = {
28         'site_id': Parameter(int, "Site identifier", primary_key=True),
29         'tenant_id': Parameter(str, "Tenant identifier"),
30         'enabled': Parameter(bool, "Has been enabled"),
31         'abbreviated_name': Parameter(str, "Abbreviated site name", max = 50),
32         'login_base': Parameter(str, "Site slice prefix", max = 20),
33         'is_public': Parameter(bool, "Publicly viewable site"),
34         'name': Parameter(str, "Full site name", max = 254),
35         'description': Parameter(str, "Description", max = 254),
36         'latitude': Parameter(float, "Decimal latitude of the site", min = -90.0, max = 90.0, nullok = True),
37         'longitude': Parameter(float, "Decimal longitude of the site", min = -180.0, max = 180.0, nullok = True),
38         'url': Parameter(str, "URL of a page that describes the site", max = 254, nullok = True),
39         'date_created': Parameter(datetime, "Date and time when site entry was created, in seconds since UNIX epoch", ro = True, default=datetime.now()), 
40         'last_updated': Parameter(datetime, "Date and time when site entry was last updated, in seconds since UNIX epoch", ro = True, nullok=True), 
41         'max_slices': Parameter(int, "Maximum number of slices that the site is able to create", default=10),
42         'max_slivers': Parameter(int, "Maximum number of slivers that the site is able to create", default=1000),
43         'person_ids': Parameter([int], "List of account identifiers", joined=True),
44         'slice_ids': Parameter([int], "List of slice identifiers", joined=True),
45         'address_ids': Parameter([int], "List of address identifiers", joined=True),
46         'pcu_ids': Parameter([int], "List of PCU identifiers", joined=True),
47         'node_ids': Parameter([int], "List of site node identifiers", joined=True),
48         'site_tag_ids' : Parameter ([int], "List of tags attached to this site", joined=True),
49         'peer_id': Parameter(int, "Peer to which this site belongs", nullok = True),
50         'peer_site_id': Parameter(int, "Foreign site identifier at peer", nullok = True),
51         'ext_consortium_id': Parameter(int, "external consortium id", nullok = True) 
52         }
53
54     def validate_last_updated(self, last_updated):
55         # always return current timestamp 
56         last_updated = datetime.now()
57         return last_updated
58
59     def validate_login_base(self, login_base):
60         if not len(login_base):
61             raise PLCInvalidArgument, "Login base must be specified"
62
63         if not set(login_base).issubset(string.lowercase + string.digits):
64             raise PLCInvalidArgument, "Login base must consist only of lowercase ASCII letters or numbers"
65
66         conflicts = Site().select(filter={'login_base': login_base})  
67         for site in conflicts:
68             if 'site_id' not in self or self['site_id'] != site.site_id:
69                 raise PLCInvalidArgument, "login_base already in use"
70
71         return login_base
72
73     def add_person(self, person_filter, role_name=None):
74         assert 'site_id' in self
75         assert 'tenant_id' in self
76         if not role_name:
77             role_name = 'user'
78         roles = Roles(self.api, role_name)
79         if not roles:
80             raise PLCInvalidArgument, "No such role %s" % role_name
81         role = roles[0]
82         tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id']) 
83         persons = Persons(self.api, person_filter)
84         for person in persons:
85             keystone_user = self.api.client_shell.keystone.users.find(id=person['keystone_id'])
86             tenant.add_user(keystone_user, role.object)  
87             site_person = SitePerson(self.api, {'site_id': self['site_id'], 
88                                                 'person_id': person['person_id']}) 
89             site_person.sync()
90
91
92     def delete_person(self, person_filter, role=None):
93         assert 'site_id' in self
94         assert 'tenant_id' in self
95         if not role:
96             role = 'user'
97         roles = Roles(self.api, role_name)
98         if not roles:
99             raise PLCInvalidArgument, "No such role %s" % role_name
100         role = roles[0]
101         tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id'])
102         persons = Persons(self.api, person_filter)
103         for person in persons:
104             keystone_user = self.api.client_shell.keystone.users.find(id=person['keystone_id'])
105             tenant.remove_user(keystone_user, role.object)
106             site_persons = SitePersons(self.api, {'site_id': self['id'],
107                                                 'person_id': person['person_id']})
108             for site_person in site_persons:
109                 site_person.delete()    
110           
111
112     def sync(self, commit=True, validate=True):
113         """
114         Add or update the site.
115         """
116         assert 'login_base' in self
117         AlchemyObj.sync(self, commit=commit, validate=validate)     
118         # filter out fields that are not supported in keystone
119         nova_fields = ['enabled', 'description']
120         nova_can_update = lambda (field, value): field in nova_fields
121         nova_site = dict(filter(nova_can_update, self.items()))
122         nova_site['tenant_name'] = self['login_base']
123         if 'site_id' not in self:
124             # check if keystone record exsits
125             tenants = self.api.client_shell.keystone.tenants.findall(name=self['login_base'])
126             if not tenants:
127                 self.object = self.api.client_shell.keystone.tenants.create(**nova_site)
128             else:
129                 self.object = tenants[0]
130             self['tenant_id'] = self.object.id
131             # sync the plc record
132             AlchemyObj.insert(self, dict(self))
133             site = AlchemyObj.select(self, filter={'tenant_id': self['tenant_id']})[0]
134             self['site_id'] = site.site_id
135              
136         else:
137             self.object = self.api.client_shell.keystone.tenants.update(self['tenant_id'], **nova_site)
138             AlchemyObj.update(self, {'site_id': self['site_id']}, dict(self))
139
140     def delete(self, commit=True):
141         assert 'site_id' in self
142         assert 'tenant_id' in self
143         
144         # delete nova object
145         tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id'])
146         self.api.client_shell.keystone.tenants.delete(tenant)
147         
148         # delete relationships
149         for site_person in SitePerson().select(filter={'site_id': self['site_id']}):
150             site_person.delete()
151         for slice in Slice().select(filter={'site_id': self['site_id']}):
152             slice.delete()
153         for pcu in PCU().select(filter={'site_id': self['site_id']}):
154             pcu.delete()
155         for node in Node().select(filter={'site_id': self['site_id']}):
156             node.delete()
157         for address in SiteAddress().select(filter={'site_id': self['site_id']}):
158             address.delete()
159         for site_tag in SiteTag().select(filter={'site_id': self['site_id']}):
160             site_tag.delete() 
161         
162         # delete site
163         AlchemyObj.delete(self, filter={'site_id': self['site_id']})        
164         
165                
166
167 class Sites(list):
168     """
169     Representation of row(s) from the sites table in the
170     database.
171     """
172
173     def __init__(self, api, site_filter = None, columns = None):
174         self.api = api 
175         self.refresh(api)
176         if not site_filter:
177             sites = Site().select() 
178         elif isinstance(site_filter, int):
179             sites = Site().select(filter={'site_id': site_filter})
180         elif isinstance(site_filter, StringTypes):
181             sites = Site().select(filter={'login_base': site_filter})
182         elif isinstance(site_filter, dict):
183             sites = Site().select(filter=site_filter)
184         elif isinstance(site_filter, (list, tuple, set)):
185             ints = filter(lambda x: isinstance(x, (int, long)), site_filter)
186             strs = filter(lambda x: isinstance(x, StringTypes), site_filter)
187             site_filter = {'site_id': ints, 'login_base': strs}
188             sites = Site().select(filter=site_filter)
189         else:
190             raise PLCInvalidArgument, "Wrong site filter %s" % site_filter         
191
192
193         for site in sites:
194             site = Site(self.api, object = site)
195             if not columns or 'person_ids' in columns:
196                 site_persons = SitePerson().select(filter={'site_id': site['site_id']})
197                 site['person_ids'] = [rec.person_id for rec in site_persons]
198
199             if not columns or 'slice_ids' in columns:
200                 site_slices = Slice().select(filter={'site_id': site['site_id']})
201                 site['slice_ids'] = [rec.slice_id for rec in site_slices]
202
203             if not columns or 'puc_ids' in columns:
204                 site_pcus = PCU().select(filter={'site_id': site['site_id']})
205                 site['pcu_ids'] = [rec.pcu_id for rec in site_pcus]
206             if not columns or 'node_ids' in columns:
207                 site_nodes = Node().select(filter={'site_id': site['site_id']})
208                 site['node_ids'] = [rec.node_id for rec in site_nodes]
209             if not columns or 'address_ids' in columns:
210                 site_addresses = SiteAddress().select(filter={'site_id': site['site_id']})
211                 site['address_ids'] = [rec.address_id for rec in site_addresses]
212
213             if not columns or 'site_tag_ids' in columns:
214                 site_tags = SiteTag().select(filter={'site_id': site['site_id']})
215                 site['site_tag_ids'] = [rec.tag_id for rec in site_tags]
216
217             self.append(site)
218
219     def refresh(self, api):
220         """
221         Import tenants from keystone.
222         """
223         # get current sites
224         sites = Site().select()
225         login_bases = [site['login_base'] for site in sites]
226
227         # get current tenants
228         tenants = api.client_shell.keystone.tenants.list()
229
230         # add tenants that dont already exist
231         for tenant in tenants:
232             # site tenants should not contain '_'
233             if '_' not in tenant.name and tenant.name not in login_bases:
234                 description = tenant.description
235                 if not description: description  = tenant.name  
236                 site = Site(api, {'login_base': tenant.name,
237                                   'tenant_id': tenant.id,
238                                   'enabled': tenant.enabled,
239                                   'description': description
240                                   'name': tenant.name,
241                                   'abbreviated_name': tenant.name,
242                                   'is_public': True})
243                 site.sync()