change designed for illustrative purposes
[plcapi.git] / PLC / Methods / AddSlice.py
1 import re
2
3 from PLC.Faults import *
4 from PLC.Auth import Auth
5 from PLC.Method import Method
6 from PLC.Parameter import Parameter, Mixed
7 from PLC.Table import Row
8
9 from PLC.Slices import Slice, Slices
10 from PLC.Sites import Site, Sites
11 from PLC.TagTypes import TagTypes
12 from PLC.SliceTags import SliceTags
13 from PLC.Methods.AddSliceTag import AddSliceTag
14 from PLC.Methods.UpdateSliceTag import UpdateSliceTag
15
16 can_update = ['name', 'instantiation', 'url', 'description', 'max_nodes']
17
18 # it would be much better to have this configured from the outside 
19 # this is not, however, the subject of the current exercise
20 # so let's keep it simple for now
21 VLAN_RANGE = range(128,255) # this would be 128 .. 254 inclusively
22
23 # also this would require the following code added in your Accessors_site.py
24 #define_accessors(current_module, Slice, 'VlanId', 'vlan_id', 
25 #                 "slice/general", "the vlan_id allocated to that slice",
26 #                  get_roles=all_roles, set_roles=admin_roles)
27 # see https://svn.planet-lab.org/wiki/TagsAndAccessors for more details
28
29 class AddSlice(Method):
30     """
31     Adds a new slice. Any fields specified in slice_fields are used,
32     otherwise defaults are used.
33
34     Valid slice names are lowercase and begin with the login_base
35     (slice prefix) of a valid site, followed by a single
36     underscore. Thereafter, only letters, numbers, or additional
37     underscores may be used.
38
39     PIs may only add slices associated with their own sites (i.e.,
40     slice prefixes must always be the login_base of one of their
41     sites).
42
43     Returns the new slice_id (> 0) if successful, faults otherwise.
44     """
45
46     roles = ['admin', 'pi']
47
48     accepted_fields = Row.accepted_fields(can_update, Slice.fields)
49     accepted_fields.update(Slice.tags)
50
51     accepts = [
52         Auth(),
53         accepted_fields
54         ]
55
56     returns = Parameter(int, 'New slice_id (> 0) if successful')
57
58     def call(self, auth, slice_fields):
59
60         [native,tags,rejected]=Row.split_fields(slice_fields,[Slice.fields,Slice.tags])
61
62         # type checking
63         native = Row.check_fields (native, self.accepted_fields)
64         if rejected:
65             raise PLCInvalidArgument, "Cannot add Slice with column(s) %r"%rejected
66
67         # Authenticated function
68         assert self.caller is not None
69
70         # 1. Lowercase.
71         # 2. Begins with login_base (letters or numbers).
72         # 3. Then single underscore after login_base.
73         # 4. Then letters, numbers, or underscores.
74         name = slice_fields['name']
75         good_name = r'^[a-z0-9]+_[a-zA-Z0-9_]+$'
76         if not name or \
77            not re.match(good_name, name):
78             raise PLCInvalidArgument, "Invalid slice name"
79
80         # Get associated site details
81         login_base = name.split("_")[0]
82         sites = Sites(self.api, [login_base])
83         if not sites:
84             raise PLCInvalidArgument, "Invalid slice prefix %s in %s"%(login_base,name)
85         site = sites[0]
86
87         #################### provisioning vlan_ids
88         # retrieve all alive slices 
89         alive_slices=Slices(self.api,{},['slice_id','name','slice_tag_ids'])
90         # build the list of their ids
91         alive_slices_ids = [ s['slice_id'] for s in alive_slices ]
92         # retrieve all the 'vlan_id' slice tags applying to these slice_ids
93         vlan_slice_tags = SliceTags (self.api,{'slice_id':alive_slices_ids,'tagname':'vlan_id'})
94         # hash them by the slice_tag_id
95         vlan_slice_tags_hashed_by_id = { slice_tag['slice_tag_id']:slice_tag for slice_tag in vlan_slice_tags }
96         # build the set of allocated vlans
97         allocated_vlan_ids = set()
98         for alive_slice in alive_slices:
99             # scan all slice tags attached to that slice
100             for slice_tag_id in alive_slice['slice_tag_ids']:
101                 # discard the one that are not 'vlan_id'
102                 if slice_tag_id not in vlan_slice_tags_hashed_by_id: continue
103                 # retrive value and convert to int
104                 slice_tag = vlan_slice_tags_hashed_by_id[slice_tag_id]
105                 if slice_tag['tagname']=='vlan_id':
106                     try: 
107                         allocated_vlan_ids.add(int(slice_tag['value']))
108                     except: 
109                         import traceback
110                         traceback.print_exc()
111                         pass
112         # find a free vlan_id
113         available=set(VLAN_RANGE) - allocated_vlan_ids
114         try:
115             assigned=available.pop()
116             # piggybacking the code below that sets tags; tags value need to be strings..
117             tags['vlan_id']=str(assigned)
118         except:
119             raise Exception,"Cannot allocate slice %s, ran out of vlan_ids"%name
120
121         if 'admin' not in self.caller['roles']:
122             if site['site_id'] not in self.caller['site_ids']:
123                 raise PLCPermissionDenied, "Slice prefix %s must match one of your sites' login_base"%login_base
124
125         if len(site['slice_ids']) >= site['max_slices']:
126             raise PLCInvalidArgument, \
127                 "Site %s has reached (%d) its maximum allowable slice count (%d)"%(site['name'],
128                                                                                    len(site['slice_ids']),
129                                                                                    site['max_slices'])
130         if not site['enabled']:
131             raise PLCInvalidArgument, "Site %s is disabled and can cannot create slices" % (site['name'])
132
133         slice = Slice(self.api, native)
134         slice['creator_person_id'] = self.caller['person_id']
135         slice['site_id'] = site['site_id']
136         slice.sync()
137
138         # Set Slice HRN
139         root_auth = self.api.config.PLC_HRN_ROOT
140         tags['hrn'] = '.'.join([root_auth, login_base, name.split("_")[1]])
141
142         for (tagname,value) in tags.iteritems():
143             # the tagtype instance is assumed to exist, just check that
144             if not TagTypes(self.api,{'tagname':tagname}):
145                 raise PLCInvalidArgument,"No such TagType %s"%tagname
146             slice_tags=SliceTags(self.api,{'tagname':tagname,'slice_id':slice['slice_id']})
147             if not slice_tags:
148                 AddSliceTag(self.api).__call__(auth,slice['slice_id'],tagname,value)
149             else:
150                 UpdateSliceTag(self.api).__call__(auth,slice_tags[0]['slice_tag_id'],value)
151
152         # take PLC_VSYS_DEFAULTS into account for convenience
153         try:
154             values= [ y for y in [ x.strip() for x in self.api.config.PLC_VSYS_DEFAULTS.split(',') ] if y ]
155             for value in values:
156                 AddSliceTag(self.api).__call__(auth,slice['slice_id'],'vsys',value)
157         except:
158             print "Could not set vsys tags as configured in PLC_VSYS_DEFAULTS"
159             import traceback
160             traceback.print_exc()
161         self.event_objects = {'Slice': [slice['slice_id']]}
162         self.message = "Slice %d created" % slice['slice_id']
163
164         return slice['slice_id']