blob: 17b2871a896cde37826b740b2e23a1e86f8a4376 [file] [log] [blame]
Stephane Barbarie6e1bd502018-11-05 22:44:45 -05001#
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 ast import literal_eval
27import shlex
28from argparse import ArgumentParser, ArgumentError
29
30from common.pon_resource_manager.resource_kv_store import ResourceKvStore
31
32
33# Used to parse extra arguments to OpenOlt adapter from the NBI
34class OltVendorArgumentParser(ArgumentParser):
35 # Must override the exit command to prevent it from
36 # calling sys.exit(). Return exception instead.
37 def exit(self, status=0, message=None):
38 raise Exception(message)
39
40
41class PONResourceManager(object):
42 """Implements APIs to initialize/allocate/release alloc/gemport/onu IDs."""
43
44 # Constants to identify resource pool
45 ONU_ID = 'ONU_ID'
46 ALLOC_ID = 'ALLOC_ID'
47 GEMPORT_ID = 'GEMPORT_ID'
48
49 # The resource ranges for a given device vendor_type should be placed
50 # at 'resource_manager/<technology>/resource_ranges/<olt_vendor_type>'
51 # path on the KV store.
52 # If Resource Range parameters are to be read from the external KV store,
53 # they are expected to be stored in the following format.
54 # Note: All parameters are MANDATORY for now.
55 '''
56 {
57 "onu_id_start": 1,
58 "onu_id_end": 127,
59 "alloc_id_start": 1024,
60 "alloc_id_end": 2816,
61 "gemport_id_start": 1024,
62 "gemport_id_end": 8960,
63 "pon_ports": 16
64 }
65
66 '''
67 # constants used as keys to reference the resource range parameters from
68 # and external KV store.
69 ONU_START_IDX = "onu_id_start"
70 ONU_END_IDX = "onu_id_end"
71 ALLOC_ID_START_IDX = "alloc_id_start"
72 ALLOC_ID_END_IDX = "alloc_id_end"
73 GEM_PORT_ID_START_IDX = "gemport_id_start"
74 GEM_PORT_ID_END_IDX = "gemport_id_end"
75 NUM_OF_PON_PORT = "pon_ports"
76
77 # PON Resource range configuration on the KV store.
78 # Format: 'resource_manager/<technology>/resource_ranges/<olt_vendor_type>'
79 # The KV store backend is initialized with a path prefix and we need to
80 # provide only the suffix.
81 PON_RESOURCE_RANGE_CONFIG_PATH = 'resource_ranges/{}'
82
83 # resource path suffix
84 ALLOC_ID_POOL_PATH = '{}/alloc_id_pool/{}'
85 GEMPORT_ID_POOL_PATH = '{}/gemport_id_pool/{}'
86 ONU_ID_POOL_PATH = '{}/onu_id_pool/{}'
87
88 # Path on the KV store for storing list of alloc IDs for a given ONU
89 # Format: <device_id>/<(pon_intf_id, onu_id)>/alloc_ids
90 ALLOC_ID_RESOURCE_MAP_PATH = '{}/{}/alloc_ids'
91
92 # Path on the KV store for storing list of gemport IDs for a given ONU
93 # Format: <device_id>/<(pon_intf_id, onu_id)>/gemport_ids
94 GEMPORT_ID_RESOURCE_MAP_PATH = '{}/{}/gemport_ids'
95
96 # Constants for internal usage.
97 PON_INTF_ID = 'pon_intf_id'
98 START_IDX = 'start_idx'
99 END_IDX = 'end_idx'
100 POOL = 'pool'
101
102 def __init__(self, technology, extra_args, device_id,
103 backend, host, port):
104 """
105 Create PONResourceManager object.
106
107 :param technology: PON technology
108 :param: extra_args: This string contains extra arguments passed during
109 pre-provisioning of OLT and specifies the OLT Vendor type
110 :param device_id: OLT device id
111 :param backend: backend store
112 :param host: ip of backend store
113 :param port: port on which backend store listens
114 :raises exception when invalid backend store passed as an argument
115 """
116 # logger
117 self._log = structlog.get_logger()
118
119 try:
120 self.technology = technology
121 self.extra_args = extra_args
122 self.device_id = device_id
123 self.backend = backend
124 self.host = host
125 self.port = port
126 self.olt_vendor = None
127 self._kv_store = ResourceKvStore(technology, device_id, backend,
128 host, port)
129 # Below attribute, pon_resource_ranges, should be initialized
130 # by reading from KV store.
131 self.pon_resource_ranges = dict()
132 except Exception as e:
133 self._log.exception("exception-in-init")
134 raise Exception(e)
135
136 def init_resource_ranges_from_kv_store(self):
137 """
138 Initialize PON resource ranges with config fetched from kv store.
139
140 :return boolean: True if PON resource ranges initialized else false
141 """
142 self.olt_vendor = self._get_olt_vendor()
143 # Try to initialize the PON Resource Ranges from KV store based on the
144 # OLT vendor key, if available
145 if self.olt_vendor is None:
146 self._log.info("olt-vendor-unavailable--not-reading-from-kv-store")
147 return False
148
149 path = self.PON_RESOURCE_RANGE_CONFIG_PATH.format(self.olt_vendor)
150 try:
151 # get resource from kv store
152 result = self._kv_store.get_from_kv_store(path)
153
154 if result is None:
155 self._log.debug("resource-range-config-unavailable-on-kvstore")
156 return False
157
158 resource_range_config = result
159
160 if resource_range_config is not None:
161 self.pon_resource_ranges = json.loads(resource_range_config)
162 self._log.debug("Init-resource-ranges-from-kvstore-success",
163 pon_resource_ranges=self.pon_resource_ranges,
164 path=path)
165 return True
166
167 except Exception as e:
168 self._log.exception("error-initializing-resource-range-from-kv-store",
169 e=e)
170 return False
171
172 def init_default_pon_resource_ranges(self, onu_start_idx=1,
173 onu_end_idx=127,
174 alloc_id_start_idx=1024,
175 alloc_id_end_idx=2816,
176 gem_port_id_start_idx=1024,
177 gem_port_id_end_idx=8960,
178 num_of_pon_ports=16):
179 """
180 Initialize default PON resource ranges
181
182 :param onu_start_idx: onu id start index
183 :param onu_end_idx: onu id end index
184 :param alloc_id_start_idx: alloc id start index
185 :param alloc_id_end_idx: alloc id end index
186 :param gem_port_id_start_idx: gemport id start index
187 :param gem_port_id_end_idx: gemport id end index
188 :param num_of_pon_ports: number of PON ports
189 """
190 self._log.info("initialize-default-resource-range-values")
191 self.pon_resource_ranges[
192 PONResourceManager.ONU_START_IDX] = onu_start_idx
193 self.pon_resource_ranges[PONResourceManager.ONU_END_IDX] = onu_end_idx
194 self.pon_resource_ranges[
195 PONResourceManager.ALLOC_ID_START_IDX] = alloc_id_start_idx
196 self.pon_resource_ranges[
197 PONResourceManager.ALLOC_ID_END_IDX] = alloc_id_end_idx
198 self.pon_resource_ranges[
199 PONResourceManager.GEM_PORT_ID_START_IDX] = gem_port_id_start_idx
200 self.pon_resource_ranges[
201 PONResourceManager.GEM_PORT_ID_END_IDX] = gem_port_id_end_idx
202 self.pon_resource_ranges[
203 PONResourceManager.NUM_OF_PON_PORT] = num_of_pon_ports
204
205 def init_device_resource_pool(self):
206 """
207 Initialize resource pool for all PON ports.
208 """
209 i = 0
210 while i < self.pon_resource_ranges[PONResourceManager.NUM_OF_PON_PORT]:
211 self.init_resource_id_pool(
212 pon_intf_id=i,
213 resource_type=PONResourceManager.ONU_ID,
214 start_idx=self.pon_resource_ranges[
215 PONResourceManager.ONU_START_IDX],
216 end_idx=self.pon_resource_ranges[
217 PONResourceManager.ONU_END_IDX])
218
219 i += 1
220
221 # TODO: ASFvOLT16 platform requires alloc and gemport ID to be unique
222 # across OLT. To keep it simple, a single pool (POOL 0) is maintained
223 # for both the resource types. This may need to change later.
224 self.init_resource_id_pool(
225 pon_intf_id=0,
226 resource_type=PONResourceManager.ALLOC_ID,
227 start_idx=self.pon_resource_ranges[
228 PONResourceManager.ALLOC_ID_START_IDX],
229 end_idx=self.pon_resource_ranges[
230 PONResourceManager.ALLOC_ID_END_IDX])
231
232 self.init_resource_id_pool(
233 pon_intf_id=0,
234 resource_type=PONResourceManager.GEMPORT_ID,
235 start_idx=self.pon_resource_ranges[
236 PONResourceManager.GEM_PORT_ID_START_IDX],
237 end_idx=self.pon_resource_ranges[
238 PONResourceManager.GEM_PORT_ID_END_IDX])
239
240 def clear_device_resource_pool(self):
241 """
242 Clear resource pool of all PON ports.
243 """
244 i = 0
245 while i < self.pon_resource_ranges[PONResourceManager.NUM_OF_PON_PORT]:
246 self.clear_resource_id_pool(
247 pon_intf_id=i,
248 resource_type=PONResourceManager.ONU_ID,
249 )
250 i += 1
251
252 self.clear_resource_id_pool(
253 pon_intf_id=0,
254 resource_type=PONResourceManager.ALLOC_ID,
255 )
256
257 self.clear_resource_id_pool(
258 pon_intf_id=0,
259 resource_type=PONResourceManager.GEMPORT_ID,
260 )
261
262 def init_resource_id_pool(self, pon_intf_id, resource_type, start_idx,
263 end_idx):
264 """
265 Initialize Resource ID pool for a given Resource Type on a given PON Port
266
267 :param pon_intf_id: OLT PON interface id
268 :param resource_type: String to identify type of resource
269 :param start_idx: start index for onu id pool
270 :param end_idx: end index for onu id pool
271 :return boolean: True if resource id pool initialized else false
272 """
273 status = False
274 path = self._get_path(pon_intf_id, resource_type)
275 if path is None:
276 return status
277
278 try:
279 # In case of adapter reboot and reconciliation resource in kv store
280 # checked for its presence if not kv store update happens
281 resource = self._get_resource(path)
282
283 if resource is not None:
284 self._log.info("Resource-already-present-in-store", path=path)
285 status = True
286 else:
287 resource = self._format_resource(pon_intf_id, start_idx,
288 end_idx)
289 self._log.info("Resource-initialized", path=path)
290
291 # Add resource as json in kv store.
292 result = self._kv_store.update_to_kv_store(path, resource)
293 if result is True:
294 status = True
295
296 except Exception as e:
297 self._log.exception("error-initializing-resource-pool", e=e)
298
299 return status
300
301 def get_resource_id(self, pon_intf_id, resource_type, num_of_id=1):
302 """
303 Create alloc/gemport/onu id for given OLT PON interface.
304
305 :param pon_intf_id: OLT PON interface id
306 :param resource_type: String to identify type of resource
307 :param num_of_id: required number of ids
308 :return list/int/None: list, int or None if resource type is
309 alloc_id/gemport_id, onu_id or invalid type
310 respectively
311 """
312 result = None
313
314 # TODO: ASFvOLT16 platform requires alloc and gemport ID to be unique
315 # across OLT. To keep it simple, a single pool (POOL 0) is maintained
316 # for both the resource types. This may need to change later.
317 # Override the incoming pon_intf_id to PON0
318 if resource_type == PONResourceManager.GEMPORT_ID or \
319 resource_type == PONResourceManager.ALLOC_ID:
320 pon_intf_id = 0
321
322 path = self._get_path(pon_intf_id, resource_type)
323 if path is None:
324 return result
325
326 try:
327 resource = self._get_resource(path)
328 if resource is not None and resource_type == \
329 PONResourceManager.ONU_ID:
330 result = self._generate_next_id(resource)
331 elif resource is not None and (
332 resource_type == PONResourceManager.GEMPORT_ID or
333 resource_type == PONResourceManager.ALLOC_ID):
334 result = list()
335 while num_of_id > 0:
336 result.append(self._generate_next_id(resource))
337 num_of_id -= 1
338 else:
339 raise Exception("get-resource-failed")
340
341 self._log.debug("Get-" + resource_type + "-success", result=result,
342 path=path)
343 # Update resource in kv store
344 self._update_resource(path, resource)
345
346 except Exception as e:
347 self._log.exception("Get-" + resource_type + "-id-failed",
348 path=path, e=e)
349 return result
350
351 def free_resource_id(self, pon_intf_id, resource_type, release_content):
352 """
353 Release alloc/gemport/onu id for given OLT PON interface.
354
355 :param pon_intf_id: OLT PON interface id
356 :param resource_type: String to identify type of resource
357 :param release_content: required number of ids
358 :return boolean: True if all IDs in given release_content released
359 else False
360 """
361 status = False
362
363 # TODO: ASFvOLT16 platform requires alloc and gemport ID to be unique
364 # across OLT. To keep it simple, a single pool (POOL 0) is maintained
365 # for both the resource types. This may need to change later.
366 # Override the incoming pon_intf_id to PON0
367 if resource_type == PONResourceManager.GEMPORT_ID or \
368 resource_type == PONResourceManager.ALLOC_ID:
369 pon_intf_id = 0
370
371 path = self._get_path(pon_intf_id, resource_type)
372 if path is None:
373 return status
374
375 try:
376 resource = self._get_resource(path)
377 if resource is not None and resource_type == \
378 PONResourceManager.ONU_ID:
379 self._release_id(resource, release_content)
380 elif resource is not None and (
381 resource_type == PONResourceManager.ALLOC_ID or
382 resource_type == PONResourceManager.GEMPORT_ID):
383 for content in release_content:
384 self._release_id(resource, content)
385 else:
386 raise Exception("get-resource-failed")
387
388 self._log.debug("Free-" + resource_type + "-success", path=path)
389
390 # Update resource in kv store
391 status = self._update_resource(path, resource)
392
393 except Exception as e:
394 self._log.exception("Free-" + resource_type + "-failed",
395 path=path, e=e)
396 return status
397
398 def clear_resource_id_pool(self, pon_intf_id, resource_type):
399 """
400 Clear Resource Pool for a given Resource Type on a given PON Port.
401
402 :return boolean: True if removed else False
403 """
404 path = self._get_path(pon_intf_id, resource_type)
405 if path is None:
406 return False
407
408 try:
409 result = self._kv_store.remove_from_kv_store(path)
410 if result is True:
411 self._log.debug("Resource-pool-cleared",
412 device_id=self.device_id,
413 path=path)
414 return True
415 except Exception as e:
416 self._log.exception("error-clearing-resource-pool", e=e)
417
418 self._log.error("Clear-resource-pool-failed", device_id=self.device_id,
419 path=path)
420 return False
421
422 def init_resource_map(self, pon_intf_onu_id):
423 """
424 Initialize resource map
425
426 :param pon_intf_onu_id: reference of PON interface id and onu id
427 """
428 # initialize pon_intf_onu_id tuple to alloc_ids map
429 alloc_id_path = PONResourceManager.ALLOC_ID_RESOURCE_MAP_PATH.format(
430 self.device_id, str(pon_intf_onu_id)
431 )
432 alloc_ids = list()
433 self._kv_store.update_to_kv_store(
434 alloc_id_path, json.dumps(alloc_ids)
435 )
436
437 # initialize pon_intf_onu_id tuple to gemport_ids map
438 gemport_id_path = PONResourceManager.GEMPORT_ID_RESOURCE_MAP_PATH.format(
439 self.device_id, str(pon_intf_onu_id)
440 )
441 gemport_ids = list()
442 self._kv_store.update_to_kv_store(
443 gemport_id_path, json.dumps(gemport_ids)
444 )
445
446 def remove_resource_map(self, pon_intf_onu_id):
447 """
448 Remove resource map
449
450 :param pon_intf_onu_id: reference of PON interface id and onu id
451 """
452 # remove pon_intf_onu_id tuple to alloc_ids map
453 alloc_id_path = PONResourceManager.ALLOC_ID_RESOURCE_MAP_PATH.format(
454 self.device_id, str(pon_intf_onu_id)
455 )
456 self._kv_store.remove_from_kv_store(alloc_id_path)
457
458 # remove pon_intf_onu_id tuple to gemport_ids map
459 gemport_id_path = PONResourceManager.GEMPORT_ID_RESOURCE_MAP_PATH.format(
460 self.device_id, str(pon_intf_onu_id)
461 )
462 self._kv_store.remove_from_kv_store(gemport_id_path)
463
464 def get_current_alloc_ids_for_onu(self, pon_intf_onu_id):
465 """
466 Get currently configured alloc ids for given pon_intf_onu_id
467
468 :param pon_intf_onu_id: reference of PON interface id and onu id
469 """
470 path = PONResourceManager.ALLOC_ID_RESOURCE_MAP_PATH.format(
471 self.device_id,
472 str(pon_intf_onu_id))
473 value = self._kv_store.get_from_kv_store(path)
474 if value is not None:
475 alloc_id_list = json.loads(value)
476 if len(alloc_id_list) > 0:
477 return alloc_id_list
478
479 return None
480
481 def get_current_gemport_ids_for_onu(self, pon_intf_onu_id):
482 """
483 Get currently configured gemport ids for given pon_intf_onu_id
484
485 :param pon_intf_onu_id: reference of PON interface id and onu id
486 """
487
488 path = PONResourceManager.GEMPORT_ID_RESOURCE_MAP_PATH.format(
489 self.device_id,
490 str(pon_intf_onu_id))
491 value = self._kv_store.get_from_kv_store(path)
492 if value is not None:
493 gemport_id_list = json.loads(value)
494 if len(gemport_id_list) > 0:
495 return gemport_id_list
496
497 return None
498
499 def update_alloc_ids_for_onu(self, pon_intf_onu_id, alloc_ids):
500 """
501 Update currently configured alloc ids for given pon_intf_onu_id
502
503 :param pon_intf_onu_id: reference of PON interface id and onu id
504 """
505 path = PONResourceManager.ALLOC_ID_RESOURCE_MAP_PATH.format(
506 self.device_id, str(pon_intf_onu_id)
507 )
508 self._kv_store.update_to_kv_store(
509 path, json.dumps(alloc_ids)
510 )
511
512 def update_gemport_ids_for_onu(self, pon_intf_onu_id, gemport_ids):
513 """
514 Update currently configured gemport ids for given pon_intf_onu_id
515
516 :param pon_intf_onu_id: reference of PON interface id and onu id
517 """
518 path = PONResourceManager.GEMPORT_ID_RESOURCE_MAP_PATH.format(
519 self.device_id, str(pon_intf_onu_id)
520 )
521 self._kv_store.update_to_kv_store(
522 path, json.dumps(gemport_ids)
523 )
524
525 def _get_olt_vendor(self):
526 """
527 Get olt vendor variant
528
529 :return: type of olt vendor
530 """
531 olt_vendor = None
532 if self.extra_args and len(self.extra_args) > 0:
533 parser = OltVendorArgumentParser(add_help=False)
534 parser.add_argument('--olt_vendor', '-o', action='store',
535 choices=['default', 'asfvolt16', 'cigolt24'],
536 default='default')
537 try:
538 args = parser.parse_args(shlex.split(self.extra_args))
539 self._log.debug('parsing-extra-arguments', args=args)
540 olt_vendor = args.olt_vendor
541 except ArgumentError as e:
542 self._log.exception('invalid-arguments: {}', e=e)
543 except Exception as e:
544 self._log.exception('option-parsing-error: {}', e=e)
545
546 return olt_vendor
547
548 def _generate_next_id(self, resource):
549 """
550 Generate unique id having OFFSET as start index.
551
552 :param resource: resource used to generate ID
553 :return int: generated id
554 """
555 pos = resource[PONResourceManager.POOL].find('0b0')
556 resource[PONResourceManager.POOL].set(1, pos)
557 return pos[0] + resource[PONResourceManager.START_IDX]
558
559 def _release_id(self, resource, unique_id):
560 """
561 Release unique id having OFFSET as start index.
562
563 :param resource: resource used to release ID
564 :param unique_id: id need to be released
565 """
566 pos = ((int(unique_id)) - resource[PONResourceManager.START_IDX])
567 resource[PONResourceManager.POOL].set(0, pos)
568
569 def _get_path(self, pon_intf_id, resource_type):
570 """
571 Get path for given resource type.
572
573 :param pon_intf_id: OLT PON interface id
574 :param resource_type: String to identify type of resource
575 :return: path for given resource type
576 """
577 path = None
578 if resource_type == PONResourceManager.ONU_ID:
579 path = self._get_onu_id_resource_path(pon_intf_id)
580 elif resource_type == PONResourceManager.ALLOC_ID:
581 path = self._get_alloc_id_resource_path(pon_intf_id)
582 elif resource_type == PONResourceManager.GEMPORT_ID:
583 path = self._get_gemport_id_resource_path(pon_intf_id)
584 else:
585 self._log.error("invalid-resource-pool-identifier")
586 return path
587
588 def _get_alloc_id_resource_path(self, pon_intf_id):
589 """
590 Get alloc id resource path.
591
592 :param pon_intf_id: OLT PON interface id
593 :return: alloc id resource path
594 """
595 return PONResourceManager.ALLOC_ID_POOL_PATH.format(
596 self.device_id, pon_intf_id)
597
598 def _get_gemport_id_resource_path(self, pon_intf_id):
599 """
600 Get gemport id resource path.
601
602 :param pon_intf_id: OLT PON interface id
603 :return: gemport id resource path
604 """
605 return PONResourceManager.GEMPORT_ID_POOL_PATH.format(
606 self.device_id, pon_intf_id)
607
608 def _get_onu_id_resource_path(self, pon_intf_id):
609 """
610 Get onu id resource path.
611
612 :param pon_intf_id: OLT PON interface id
613 :return: onu id resource path
614 """
615 return PONResourceManager.ONU_ID_POOL_PATH.format(
616 self.device_id, pon_intf_id)
617
618 def _update_resource(self, path, resource):
619 """
620 Update resource in resource kv store.
621
622 :param path: path to update resource
623 :param resource: resource need to be updated
624 :return boolean: True if resource updated in kv store else False
625 """
626 resource[PONResourceManager.POOL] = \
627 resource[PONResourceManager.POOL].bin
628 result = self._kv_store.update_to_kv_store(path, json.dumps(resource))
629 if result is True:
630 return True
631 return False
632
633 def _get_resource(self, path):
634 """
635 Get resource from kv store.
636
637 :param path: path to get resource
638 :return: resource if resource present in kv store else None
639 """
640 # get resource from kv store
641 result = self._kv_store.get_from_kv_store(path)
642 if result is None:
643 return result
644 self._log.info("dumping resource", result=result)
645 resource = result
646
647 if resource is not None:
648 # decode resource fetched from backend store to dictionary
649 resource = json.loads(resource)
650
651 # resource pool in backend store stored as binary string whereas to
652 # access the pool to generate/release IDs it need to be converted
653 # as BitArray
654 resource[PONResourceManager.POOL] = \
655 BitArray('0b' + resource[PONResourceManager.POOL])
656
657 return resource
658
659 def _format_resource(self, pon_intf_id, start_idx, end_idx):
660 """
661 Format resource as json.
662
663 :param pon_intf_id: OLT PON interface id
664 :param start_idx: start index for id pool
665 :param end_idx: end index for id pool
666 :return dictionary: resource formatted as dictionary
667 """
668 # Format resource as json to be stored in backend store
669 resource = dict()
670 resource[PONResourceManager.PON_INTF_ID] = pon_intf_id
671 resource[PONResourceManager.START_IDX] = start_idx
672 resource[PONResourceManager.END_IDX] = end_idx
673
674 # resource pool stored in backend store as binary string
675 resource[PONResourceManager.POOL] = BitArray(end_idx).bin
676
677 return json.dumps(resource)