blob: 4abead3c2051216b5b810ffacfbda78a8bd2dc0d [file] [log] [blame]
Girish Gowdruf134c712018-08-27 04:11:07 -07001#
2# Copyright 2018 the original author or authors.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17"""
18Resource Manager will be unique for each OLT device.
19
20It exposes APIs to create/free alloc_ids/onu_ids/gemport_ids. Resource Manager
21uses a KV store in backend to ensure resiliency of the data.
22"""
23import json
24import structlog
25from bitstring import BitArray
26from twisted.internet.defer import returnValue, inlineCallbacks
27
28from common.kvstore.kvstore import create_kv_client
29from common.utils.asleep import asleep
30
31
32class PONResourceManager(object):
33 """Implements APIs to initialize/allocate/release alloc/gemport/onu IDs."""
34
35 # Constants to identify resource pool
36 ONU_ID = 'ONU_ID'
37 ALLOC_ID = 'ALLOC_ID'
38 GEMPORT_ID = 'GEMPORT_ID'
39
40 # The resource ranges for a given device vendor_type should be placed
41 # at 'resource_manager/<technology>/resource_ranges/<olt_vendor_type>'
42 # path on the KV store.
43 # If Resource Range parameters are to be read from the external KV store,
44 # they are expected to be stored in the following format.
45 # Note: All parameters are MANDATORY for now.
46 '''
47 {
48 "onu_start_idx": 1,
49 "onu_end_idx": 127,
50 "alloc_id_start_idx": 1024,
51 "alloc_id_end_idx": 65534,
52 "gem_port_id_start_idx": 1024,
53 "gem_port_id_end_idx": 16383,
54 "num_of_pon_port": 16
55 }
56 '''
57 # constants used as keys to reference the resource range parameters from
58 # and external KV store.
59 ONU_START_IDX = "onu_start_idx"
60 ONU_END_IDX = "onu_end_idx"
61 ALLOC_ID_START_IDX = "alloc_id_start_idx"
62 ALLOC_ID_END_IDX = "alloc_id_end_idx"
63 GEM_PORT_ID_START_IDX = "gem_port_id_start_idx"
64 GEM_PORT_ID_END_IDX = "gem_port_id_end_idx"
65 NUM_OF_PON_PORT = "num_of_pon_port"
66
67 # PON Resource range configuration on the KV store.
68 # Format: 'resource_manager/<technology>/resource_ranges/<olt_vendor_type>'
69 PON_RESOURCE_RANGE_CONFIG_PATH = 'resource_manager/{}/resource_ranges/{}'
70
71 # resource path in kv store
72 ALLOC_ID_POOL_PATH = 'resource_manager/{}/{}/alloc_id_pool/{}'
73 GEMPORT_ID_POOL_PATH = 'resource_manager/{}/{}/gemport_id_pool/{}'
74 ONU_ID_POOL_PATH = 'resource_manager/{}/{}/onu_id_pool/{}'
75
76 # Constants for internal usage.
77 PON_INTF_ID = 'pon_intf_id'
78 START_IDX = 'start_idx'
79 END_IDX = 'end_idx'
80 POOL = 'pool'
81
82 def __init__(self, technology, olt_vendor_type, device_id,
83 backend, host, port):
84 """
85 Create PONResourceManager object.
86
87 :param technology: PON technology
88 :param: olt_vendor_type: This string defines the OLT vendor type
89 and is used as a Key to load the resource range configuration from
90 KV store location.
91 :param device_id: OLT device id
92 :param backend: backend store
93 :param host: ip of backend store
94 :param port: port on which backend store listens
95 :raises exception when invalid backend store passed as an argument
96 """
97 # logger
98 self._log = structlog.get_logger()
99
100 try:
101 self._kv_store = create_kv_client(backend, host, port)
102 self.technology = technology
103 self.olt_vendor_type = olt_vendor_type
104 self.device_id = device_id
105 # Below attribute, pon_resource_ranges, should be initialized
106 # by reading from KV store.
107 self.pon_resource_ranges = dict()
108 except Exception as e:
109 self._log.exception("exception-in-init")
110 raise Exception(e)
111
112 @inlineCallbacks
113 def init_pon_resource_ranges(self):
114 # Try to initialize the PON Resource Ranges from KV store if available
115 status = yield self.init_resource_ranges_from_kv_store()
116 # If reading from KV store fails, initialize to default values.
117 if not status:
118 self._log.error("failed-to-read-resource-ranges-from-kv-store")
119 self.init_default_pon_resource_ranges()
120
121 @inlineCallbacks
122 def init_resource_ranges_from_kv_store(self):
123 path = self.PON_RESOURCE_RANGE_CONFIG_PATH.format(
124 self.technology, self.olt_vendor_type)
125 # get resource from kv store
126 result = yield self._kv_store.get(path)
127 resource_range_config = result[0]
128
129 if resource_range_config is not None:
130 self.pon_resource_ranges = eval(resource_range_config.value)
131 self._log.debug("Init-resource-ranges-from-kvstore-success",
132 pon_resource_ranges=self.pon_resource_ranges,
133 path=path)
134 returnValue(True)
135
136 returnValue(False)
137
138 def init_default_pon_resource_ranges(self, onu_start_idx=1,
139 onu_end_idx=127,
140 alloc_id_start_idx=1024,
141 alloc_id_end_idx=65534,
142 gem_port_id_start_idx=1024,
143 gem_port_id_end_idx=16383,
144 num_of_pon_ports=16):
145 self._log.info("initialize-default-resource-range-values")
146 self.pon_resource_ranges[PONResourceManager.ONU_START_IDX] = onu_start_idx
147 self.pon_resource_ranges[PONResourceManager.ONU_END_IDX] = onu_end_idx
148 self.pon_resource_ranges[PONResourceManager.ALLOC_ID_START_IDX] = alloc_id_start_idx
149 self.pon_resource_ranges[PONResourceManager.ALLOC_ID_END_IDX] = alloc_id_end_idx
150 self.pon_resource_ranges[
151 PONResourceManager.GEM_PORT_ID_START_IDX] = gem_port_id_start_idx
152 self.pon_resource_ranges[
153 PONResourceManager.GEM_PORT_ID_END_IDX] = gem_port_id_end_idx
154 self.pon_resource_ranges[PONResourceManager.NUM_OF_PON_PORT] = num_of_pon_ports
155
156 def init_device_resource_pool(self):
157 i = 0
158 while i < self.pon_resource_ranges[PONResourceManager.NUM_OF_PON_PORT]:
159 self.init_resource_id_pool(
160 pon_intf_id=i,
161 resource_type=PONResourceManager.ONU_ID,
162 start_idx=self.pon_resource_ranges[
163 PONResourceManager.ONU_START_IDX],
164 end_idx=self.pon_resource_ranges[
165 PONResourceManager.ONU_END_IDX])
166
167 self.init_resource_id_pool(
168 pon_intf_id=i,
169 resource_type=PONResourceManager.ALLOC_ID,
170 start_idx=self.pon_resource_ranges[
171 PONResourceManager.ALLOC_ID_START_IDX],
172 end_idx=self.pon_resource_ranges[
173 PONResourceManager.ALLOC_ID_END_IDX])
174
175 self.init_resource_id_pool(
176 pon_intf_id=i,
177 resource_type=PONResourceManager.GEMPORT_ID,
178 start_idx=self.pon_resource_ranges[
179 PONResourceManager.GEM_PORT_ID_START_IDX],
180 end_idx=self.pon_resource_ranges[
181 PONResourceManager.GEM_PORT_ID_END_IDX])
182 i += 1
183
184 def clear_device_resource_pool(self):
185 i = 0
186 while i < self.pon_resource_ranges[PONResourceManager.NUM_OF_PON_PORT]:
187 self.clear_resource_id_pool(
188 pon_intf_id=i,
189 resource_type=PONResourceManager.ONU_ID,
190 )
191
192 self.clear_resource_id_pool(
193 pon_intf_id=i,
194 resource_type=PONResourceManager.ALLOC_ID,
195 )
196
197 self.clear_resource_id_pool(
198 pon_intf_id=i,
199 resource_type=PONResourceManager.GEMPORT_ID,
200 )
201 i += 1
202
203 @inlineCallbacks
204 def init_resource_id_pool(self, pon_intf_id, resource_type, start_idx,
205 end_idx):
206 """
207 Initialize Resource ID pool for a given Resource Type on a given PON Port
208
209 :param pon_intf_id: OLT PON interface id
210 :param resource_type: String to identify type of resource
211 :param start_idx: start index for onu id pool
212 :param end_idx: end index for onu id pool
213 :return boolean: True if resource id pool initialized else false
214 """
215 status = False
216 path = self._get_path(pon_intf_id, resource_type)
217 if path is None:
218 returnValue(status)
219
220 # In case of adapter reboot and reconciliation resource in kv store
221 # checked for its presence if not kv store update happens
222 resource = yield self._get_resource(path)
223
224 if resource is not None:
225 self._log.info("Resource-already-present-in-store", path=path)
226 status = True
227 else:
228 resource = self._format_resource(pon_intf_id, start_idx, end_idx)
229 self._log.info("Resource-initialized", path=path)
230
231 # Add resource as json in kv store.
232 result = yield self._kv_store.put(path, resource)
233 if result is None:
234 status = True
235 returnValue(status)
236
237 @inlineCallbacks
238 def get_resource_id(self, pon_intf_id, resource_type, num_of_id=1):
239 """
240 Create alloc/gemport/onu id for given OLT PON interface.
241
242 :param pon_intf_id: OLT PON interface id
243 :param resource_type: String to identify type of resource
244 :param num_of_id: required number of ids
245 :return list/int/None: list, int or None if resource type is
246 alloc_id/gemport_id, onu_id or invalid type
247 respectively
248 """
249 result = None
250 path = self._get_path(pon_intf_id, resource_type)
251 if path is None:
252 returnValue(result)
253
254 resource = yield self._get_resource(path)
255 try:
256 if resource is not None and resource_type == \
257 PONResourceManager.ONU_ID:
258 result = self._generate_next_id(resource)
259 elif resource is not None and (
260 resource_type == PONResourceManager.GEMPORT_ID or
261 resource_type == PONResourceManager.ALLOC_ID):
262 result = list()
263 while num_of_id > 0:
264 result.append(self._generate_next_id(resource))
265 num_of_id -= 1
266
267 # Update resource in kv store
268 self._update_resource(path, resource)
269
270 except BaseException:
271 self._log.exception("Get-" + resource_type + "-id-failed",
272 path=path)
273 self._log.debug("Get-" + resource_type + "-success", result=result,
274 path=path)
275 returnValue(result)
276
277 @inlineCallbacks
278 def free_resource_id(self, pon_intf_id, resource_type, release_content):
279 """
280 Release alloc/gemport/onu id for given OLT PON interface.
281
282 :param pon_intf_id: OLT PON interface id
283 :param resource_type: String to identify type of resource
284 :param release_content: required number of ids
285 :return boolean: True if all IDs in given release_content released
286 else False
287 """
288 status = False
289 path = self._get_path(pon_intf_id, resource_type)
290 if path is None:
291 returnValue(status)
292
293 resource = yield self._get_resource(path)
294 try:
295 if resource is not None and resource_type == \
296 PONResourceManager.ONU_ID:
297 self._release_id(resource, release_content)
298 elif resource is not None and (
299 resource_type == PONResourceManager.ALLOC_ID or
300 resource_type == PONResourceManager.GEMPORT_ID):
301 for content in release_content:
302 self._release_id(resource, content)
303 self._log.debug("Free-" + resource_type + "-success", path=path)
304
305 # Update resource in kv store
306 status = yield self._update_resource(path, resource)
307
308 except BaseException:
309 self._log.exception("Free-" + resource_type + "-failed", path=path)
310 returnValue(status)
311
312 @inlineCallbacks
313 def clear_resource_id_pool(self, pon_intf_id, resource_type):
314 """
315 Clear Resource Pool for a given Resource Type on a given PON Port.
316
317 :return boolean: True if removed else False
318 """
319 path = self._get_path(pon_intf_id, resource_type)
320 if path is None:
321 returnValue(False)
322
323 result = yield self._kv_store.delete(path)
324 if result is None:
325 self._log.debug("Resource-pool-cleared", device_id=self.device_id,
326 path=path)
327 returnValue(True)
328 self._log.error("Clear-resource-pool-failed", device_id=self.device_id,
329 path=path)
330 returnValue(False)
331
332 def _generate_next_id(self, resource):
333 """
334 Generate unique id having OFFSET as start index.
335
336 :param resource: resource used to generate ID
337 :return int: generated id
338 """
339 pos = resource[PONResourceManager.POOL].find('0b0')
340 resource[PONResourceManager.POOL].set(1, pos)
341 return pos[0] + resource[PONResourceManager.START_IDX]
342
343 def _release_id(self, resource, unique_id):
344 """
345 Release unique id having OFFSET as start index.
346
347 :param resource: resource used to release ID
348 :param unique_id: id need to be released
349 """
350 pos = ((int(unique_id)) - resource[PONResourceManager.START_IDX])
351 resource[PONResourceManager.POOL].set(0, pos)
352
353 def _get_path(self, pon_intf_id, resource_type):
354 """
355 Get path for given resource type.
356
357 :param pon_intf_id: OLT PON interface id
358 :param resource_type: String to identify type of resource
359 :return: path for given resource type
360 """
361 path = None
362 if resource_type == PONResourceManager.ONU_ID:
363 path = self._get_onu_id_resource_path(pon_intf_id)
364 elif resource_type == PONResourceManager.ALLOC_ID:
365 path = self._get_alloc_id_resource_path(pon_intf_id)
366 elif resource_type == PONResourceManager.GEMPORT_ID:
367 path = self._get_gemport_id_resource_path(pon_intf_id)
368 else:
369 self._log.error("invalid-resource-pool-identifier")
370 return path
371
372 def _get_alloc_id_resource_path(self, pon_intf_id):
373 """
374 Get alloc id resource path.
375
376 :param pon_intf_id: OLT PON interface id
377 :return: alloc id resource path
378 """
379 return PONResourceManager.ALLOC_ID_POOL_PATH.format(
380 self.technology, self.device_id, pon_intf_id)
381
382 def _get_gemport_id_resource_path(self, pon_intf_id):
383 """
384 Get gemport id resource path.
385
386 :param pon_intf_id: OLT PON interface id
387 :return: gemport id resource path
388 """
389 return PONResourceManager.GEMPORT_ID_POOL_PATH.format(
390 self.technology, self.device_id, pon_intf_id)
391
392 def _get_onu_id_resource_path(self, pon_intf_id):
393 """
394 Get onu id resource path.
395
396 :param pon_intf_id: OLT PON interface id
397 :return: onu id resource path
398 """
399 return PONResourceManager.ONU_ID_POOL_PATH.format(
400 self.technology, self.device_id, pon_intf_id)
401
402 @inlineCallbacks
403 def _update_resource(self, path, resource):
404 """
405 Update resource in resource kv store.
406
407 :param path: path to update resource
408 :param resource: resource need to be updated
409 :return boolean: True if resource updated in kv store else False
410 """
411 resource[PONResourceManager.POOL] = \
412 resource[PONResourceManager.POOL].bin
413 result = yield self._kv_store.put(path, json.dumps(resource))
414 if result is None:
415 returnValue(True)
416 returnValue(False)
417
418 @inlineCallbacks
419 def _get_resource(self, path):
420 """
421 Get resource from kv store.
422
423 :param path: path to get resource
424 :return: resource if resource present in kv store else None
425 """
426 # get resource from kv store
427 result = yield self._kv_store.get(path)
428 resource = result[0]
429
430 if resource is not None:
431 # decode resource fetched from backend store to dictionary
432 resource = eval(resource.value)
433
434 # resource pool in backend store stored as binary string whereas to
435 # access the pool to generate/release IDs it need to be converted
436 # as BitArray
437 resource[PONResourceManager.POOL] = \
438 BitArray('0b' + resource[PONResourceManager.POOL])
439
440 returnValue(resource)
441
442 def _format_resource(self, pon_intf_id, start_idx, end_idx):
443 """
444 Format resource as json.
445
446 :param pon_intf_id: OLT PON interface id
447 :param start_idx: start index for id pool
448 :param end_idx: end index for id pool
449 :return dictionary: resource formatted as dictionary
450 """
451 # Format resource as json to be stored in backend store
452 resource = dict()
453 resource[PONResourceManager.PON_INTF_ID] = pon_intf_id
454 resource[PONResourceManager.START_IDX] = start_idx
455 resource[PONResourceManager.END_IDX] = end_idx
456
457 # resource pool stored in backend store as binary string
458 resource[PONResourceManager.POOL] = BitArray(end_idx).bin
459
460 return json.dumps(resource)