added refresh()
[plcapi.git] / PLC / Slices.py
1 from types import StringTypes
2 import time
3 import re
4 from datetime import datetime, timedelta
5
6 from PLC.Faults import *
7 from PLC.Parameter import Parameter, Mixed
8 from PLC.Debug import profile
9 from PLC.Nodes import Node
10 from PLC.Persons import Person, Persons
11 from PLC.SlicePersons import SlicePerson, SlicePersons
12 from PLC.SliceNodes import SliceNode, SliceNodes
13 from PLC.SliceTags import SliceTag, SliceTags
14 from PLC.Timestamp import Timestamp
15 from PLC.Storage.AlchemyObject import AlchemyObj
16
17 class Slice(AlchemyObj):
18     """
19     Representation of a row in the slices table. To use, optionally
20     instantiate with a dict of values. Update as you would a
21     dict. Commit to the database with sync().To use, instantiate
22     with a dict of values.
23     """
24
25     tablename = 'slices'
26  
27     fields = {
28         'slice_id': Parameter(int, "Slice identifier", primary_key=True),
29         'site_id': Parameter(int, "Identifier of the site to which this slice belongs"),
30         'tenant_id': Parameter(int, "Keystone tenant identifier"), 
31         'name': Parameter(str, "Slice name", max = 32),
32         'instantiation': Parameter(str, "Slice instantiation state", nullok=True),
33         'url': Parameter(str, "URL further describing this slice", max = 254, nullok = True),
34         'description': Parameter(str, "Slice description", max = 2048, nullok = True),
35         'max_nodes': Parameter(int, "Maximum number of nodes that can be assigned to this slice", default=100),
36         'creator_person_id': Parameter(str, "Identifier of the account that created this slice"),
37         'created': Parameter(datetime, "Date and time when slice was created, in seconds since UNIX epoch", ro = True),
38         'expires': Parameter(datetime, "Date and time when slice expires, in seconds since UNIX epoch"),
39         'node_ids': Parameter([str], "List of nodes in this slice", joined = True),
40         'person_ids': Parameter([str], "List of accounts that can use this slice", joined = True),
41         'slice_tag_ids': Parameter([int], "List of slice attributes", joined = True),
42         'peer_id': Parameter(int, "Peer to which this slice belongs", nullok = True),
43         'peer_slice_id': Parameter(int, "Foreign slice identifier at peer", nullok = True),
44         }
45     tags = {}
46
47     def validate_name(self, name):
48         # N.B.: Responsibility of the caller to ensure that login_base
49         # portion of the slice name corresponds to a valid site, if
50         # desired.
51
52         # 1. Lowercase.
53         # 2. Begins with login_base (letters or numbers).
54         # 3. Then single underscore after login_base.
55         # 4. Then letters, numbers, or underscores.
56         good_name = r'^[a-z0-9]+_[a-zA-Z0-9_]+$'
57         if not name or \
58            not re.match(good_name, name):
59             raise PLCInvalidArgument, "Invalid slice name"
60
61         conflicts = Slices(self.api, [name])
62         for slice in conflicts:
63             if 'slice_id' not in self or self['slice_id'] != slice.slice_id:
64                 raise PLCInvalidArgument, "Slice name already in use, %s"%name
65
66         return name
67
68     def validate_expires(self, expires):
69         # N.B.: Responsibility of the caller to ensure that expires is
70         # not too far into the future.
71         check_future = not ('is_deleted' in self and self['is_deleted'])
72         return Timestamp.sql_validate( expires, check_future = check_future)
73
74     def add_person(self, person_filter, role_name=None):
75         assert 'slice_id' in self
76         assert 'tenant_id' in self
77         if not role_name:
78             role_name = 'user'
79         roles = Roles(self.api, role_name)
80         if not roles:
81             raise PLCInvalidArgument, "No such role %s" % role_name
82         role = roles[0]
83         tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id'])
84         persons = Persons(self.api, person_filter)
85         for person in persons:
86             keystone_user = self.api.client_shell.keystone.users.find(id=person['keystone_id'])
87             tenant.add_user(keystone_user, role.object)
88             slice_person = SlicePerson(self.api, {'slice_id': self['slice_id'],
89                                                   'person_id': person['person_id']})
90             slice_person.sync()
91
92     def remove_person(self, person_filter, role=None):
93         assert 'slice_id' in self
94         assert 'tenant_id' in self
95         if not role_name:
96             role_name = '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             slice_person = SlicePerson(self.api, {'slice_id': self['slice_id'],
107                                                   'person_id': person['person_id']})
108             slice_person.delete()
109  
110
111     def add_node(self, node_filter):
112         assert 'slice_id' in self
113         nodes = Nodes(self.api, node_filter)
114         for node in nodes:
115             slice_node = SliceNode(self.api, {'slice_id': self['slice_id'],
116                                               'node_id': node['node_id']})
117             slice_node.sync()
118  
119     def remove_node(self, node_filter):
120         assert 'slice_id' in self
121         nodes = Nodes(self.api, node_filter)
122         for node in nodes:
123             slice_node = SliceNode(self.api, {'slice_id': self['slice_id'],
124                                               'node_id': node['node_id']})
125             slice_node.delete()
126
127     #add_to_node_whitelist = Row.add_object(Node, 'node_slice_whitelist')
128     #delete_from_node_whitelist = Row.remove_object(Node, 'node_slice_whitelist')
129
130     def sync(self, commit = True, validate=True):
131         """
132         Add or update a slice.
133         """
134         # sync the nova record and the plc record
135         AlchemyObj.sync(self, commit=commit, validate=validate)
136         # create the nova record
137         nova_fields = ['enabled', 'description']
138         nova_can_update = lambda (field, value): field in nova_fields
139         nova_slice = dict(filter(nova_can_update, self.items()))
140         nova_slice['tenant_name'] = self['name']
141         if 'slice_id' not in self:
142             now = datetime.now()
143             # Before a new slice is added, delete expired slices
144             #expired = Slices(self.api, expires = -int(time.time()))
145             #for slice in expired:
146             #    slice.delete(commit)
147             self.object = self.api.client_shell.keystone.tenants.create(**nova_slice)
148             self['tenant_id'] = self.object.id
149             self['created'] = now
150             self['expires'] = now + timedelta(days=14)
151             AlchemyObj.insert(self, dict(self))
152             slice = AlchemyObj.select(self, filter={'tenant_id': self['tenant_id']})[0]
153             self['slice_id'] = slice.slice_id
154         else:
155             self.object = self.api.client_shell.keystone.tenants.update(self['tenant_id'], **nova_slice) 
156             AlchemyObj.update(self, {'slice_id': self['slice_id']}, dict(self)) 
157
158     def delete(self, commit = True):
159         """
160         Delete existing slice.
161         """
162         assert 'slice_id' in self
163         assert 'tenant_id' in self
164
165         # delete the nova object
166         tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id'])
167         self.api.client_shell.keystone.tenants.delete(tenant)
168
169         # delete relationships
170         for slice_person in SlicePerson().select(filter={'slice_id': self['slice_id']}):
171             slice_person.delete()
172         for slice_node in SliceNode().select(filter={'slice_id': self['slice_id']}):
173             slice_node.delete()
174         for slice_tag in SliceTag().select(filter={'slice_id': self['slice_id']}):
175             slice_tag.delete()
176         
177         # delete slice 
178         AlchemyObj.delete(self, filter={'slice_id': self['slice_id']})
179
180 class Slices(list):
181     """
182     Representation of row(s) from the slices table in the
183     database.
184     """
185
186     def __init__(self, api, slice_filter = None, columns = None, expires = int(time.time())):
187          
188         # the view that we're selecting upon: start with view_slices
189         if not slice_filter:
190             slices = Slice().select()
191         elif isinstance (slice_filter, StringTypes):
192             slices = Slice().select(filter={'name': slice_filter})
193         elif isinstance(slice_filter, dict):
194             slices = Slice().select(filter=slice_filter)
195         elif isinstance(slice_filter, (list, tuple, set)):
196             slices = Slice().select()
197             slices = [slice for slice in slices if slice.slice_id in slice_filter or slice.name in slice_filter]
198         else:
199             raise PLCInvalidArgument, "Wrong slice filter %r"%slice_filter
200
201         for slice in slices:
202             slice = Slice(api, object=slice)
203             if not columns or 'person_ids' in columns:
204                 slice_persons = SlicePerson().select(filter={'slice_id': slice['slice_id']})
205                 slice['person_ids'] = [rec.person_id for rec in slice_persons] 
206                 
207             if not columns or 'node_ids' in columns:
208                 slice_nodes = SliceNode().select(filter={'slice_id': slice['slice_id']})
209                 slice['node_ids'] = [rec.node_id for rec in slice_nodes]
210
211             if not columns or 'slice_tag_ids' in columns:
212                 slice_tags = SliceTag().select(filter={'slice_id': slice['slice_id']})
213                 slice['slice_tag_ids'] = [rec.slice_tag_id for rec in slice_tags]
214                 
215             self.append(slice)