131d2154e53134e301e935497718afbb53c0c67a
[genius.git] / idmanager / idmanager-impl / src / main / java / org / opendaylight / genius / idmanager / IdManager.java
1 /*
2  * Copyright (c) 2016 Ericsson India Global Services Pvt Ltd. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.genius.idmanager;
10
11 import static java.util.Comparator.comparing;
12 import static java.util.stream.Collectors.toCollection;
13 import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.CONFIGURATION;
14
15 import com.google.common.base.Optional;
16 import com.google.common.util.concurrent.ListenableFuture;
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.LinkedList;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Timer;
24 import java.util.TimerTask;
25 import java.util.concurrent.CompletableFuture;
26 import java.util.concurrent.ConcurrentHashMap;
27 import java.util.concurrent.ConcurrentMap;
28 import java.util.concurrent.CopyOnWriteArrayList;
29 import java.util.concurrent.CountDownLatch;
30 import java.util.concurrent.ExecutionException;
31 import java.util.concurrent.Future;
32 import java.util.concurrent.TimeUnit;
33 import javax.annotation.PostConstruct;
34 import javax.annotation.PreDestroy;
35 import javax.inject.Inject;
36 import javax.inject.Singleton;
37 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
38 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
39 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
40 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
41 import org.opendaylight.daexim.DataImportBootReady;
42 import org.opendaylight.genius.datastoreutils.SingleTransactionDataBroker;
43 import org.opendaylight.genius.idmanager.ReleasedIdHolder.DelayedIdEntry;
44 import org.opendaylight.genius.idmanager.api.IdManagerMonitor;
45 import org.opendaylight.genius.idmanager.jobs.CleanUpJob;
46 import org.opendaylight.genius.idmanager.jobs.IdHolderSyncJob;
47 import org.opendaylight.genius.idmanager.jobs.LocalPoolCreateJob;
48 import org.opendaylight.genius.idmanager.jobs.LocalPoolDeleteJob;
49 import org.opendaylight.genius.idmanager.jobs.UpdateIdEntryJob;
50 import org.opendaylight.infrautils.jobcoordinator.JobCoordinator;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdInput;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdOutput;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdOutputBuilder;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdRangeInput;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdRangeOutput;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdRangeOutputBuilder;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.CreateIdPoolInput;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.DeleteIdPoolInput;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.IdManagerService;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.IdPools;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.ReleaseIdInput;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.id.pools.IdPool;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.id.pools.IdPoolBuilder;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.id.pools.IdPoolKey;
65 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.id.pools.id.pool.AvailableIdsHolder;
66 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.id.pools.id.pool.AvailableIdsHolderBuilder;
67 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.id.pools.id.pool.ChildPools;
68 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.id.pools.id.pool.IdEntries;
69 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.id.pools.id.pool.ReleasedIdsHolder;
70 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.id.pools.id.pool.ReleasedIdsHolderBuilder;
71 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.released.ids.DelayedIdEntries;
72 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.lockmanager.rev160413.LockManagerService;
73 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
74 import org.opendaylight.yangtools.yang.common.OperationFailedException;
75 import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
76 import org.opendaylight.yangtools.yang.common.RpcResult;
77 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
78 import org.ops4j.pax.cdi.api.OsgiService;
79 import org.slf4j.Logger;
80 import org.slf4j.LoggerFactory;
81
82 @Singleton
83 public class IdManager implements IdManagerService, IdManagerMonitor {
84
85     private static final Logger LOG = LoggerFactory.getLogger(IdManager.class);
86     private static final long DEFAULT_IDLE_TIME = 24 * 60 * 60;
87
88     private final DataBroker broker;
89     private final SingleTransactionDataBroker singleTxDB;
90     private final LockManagerService lockManager;
91     private final IdUtils idUtils;
92     private final JobCoordinator jobCoordinator;
93
94     private final ConcurrentMap<String, IdLocalPool> localPool;
95     private final Timer cleanJobTimer = new Timer();
96
97     @Inject
98     public IdManager(DataBroker db, LockManagerService lockManager, IdUtils idUtils,
99             @OsgiService DataImportBootReady dataImportBootReady, JobCoordinator jobCoordinator)
100                     throws ReadFailedException {
101         this.broker = db;
102         this.singleTxDB = new SingleTransactionDataBroker(db);
103         this.lockManager = lockManager;
104         this.idUtils = idUtils;
105         this.jobCoordinator = jobCoordinator;
106
107         // NB: We do not "use" the DataImportBootReady, but it's presence in the OSGi
108         // Service Registry is the required "signal" that the Daexim "import on boot"
109         // has fully completed (which we want to wait for).  Therefore, making this
110         // dependent on that defers the Blueprint initialization, as we'd like to,
111         // so that we do not start giving out new IDs before an import went in.
112         // Thus, please DO NOT remove the DataImportBootReady argument, even if
113         // it appears to be (is) un-used from a Java code PoV!
114
115         this.localPool = new ConcurrentHashMap<>();
116         populateCache();
117     }
118
119     @Override
120     public Map<String, String> getLocalPoolsDetails() {
121         Map<String, String> map = new HashMap<>();
122         localPool.entrySet().stream().forEach(entry -> map.put(entry.getKey(), entry.getValue().toString()));
123         return map;
124     }
125
126     @PostConstruct
127     public void start() {
128         LOG.info("{} start", getClass().getSimpleName());
129     }
130
131     @PreDestroy
132     public void close() {
133         cleanJobTimer.cancel();
134
135         LOG.info("{} close", getClass().getSimpleName());
136     }
137
138     private void populateCache() throws ReadFailedException {
139         // If IP changes during reboot, then there will be orphaned child pools.
140         InstanceIdentifier<IdPools> idPoolsInstance = idUtils.getIdPools();
141         Optional<IdPools> idPoolsOptional = singleTxDB.syncReadOptional(CONFIGURATION, idPoolsInstance);
142         if (!idPoolsOptional.isPresent()) {
143             return;
144         }
145         IdPools idPools = idPoolsOptional.get();
146         List<IdPool> idPoolList = idPools.getIdPool();
147         idPoolList
148                 .stream()
149                 .filter(idPool -> idPool.getParentPoolName() != null
150                         && !idPool.getParentPoolName().isEmpty()
151                         && idUtils.getLocalPoolName(idPool.getParentPoolName())
152                                 .equals(idPool.getPoolName()))
153                 .forEach(
154                     idPool -> updateLocalIdPoolCache(idPool,
155                         idPool.getParentPoolName()));
156     }
157
158     public boolean updateLocalIdPoolCache(IdPool idPool, String parentPoolName) {
159         AvailableIdsHolder availableIdsHolder = idPool.getAvailableIdsHolder();
160         AvailableIdHolder availableIdHolder = new AvailableIdHolder(idUtils, availableIdsHolder.getStart(),
161                 availableIdsHolder.getEnd());
162         availableIdHolder.setCur(availableIdsHolder.getCursor());
163         ReleasedIdsHolder releasedIdsHolder = idPool.getReleasedIdsHolder();
164         ReleasedIdHolder releasedIdHolder = new ReleasedIdHolder(idUtils, releasedIdsHolder.getDelayedTimeSec());
165         releasedIdHolder.setAvailableIdCount(releasedIdsHolder.getAvailableIdCount());
166         List<DelayedIdEntries> delayedEntries = releasedIdsHolder.getDelayedIdEntries();
167         List<DelayedIdEntry> delayedIdEntryInCache = new CopyOnWriteArrayList<>();
168         delayedIdEntryInCache = delayedEntries
169                 .stream()
170                 .map(delayedIdEntry -> new DelayedIdEntry(delayedIdEntry
171                         .getId(), delayedIdEntry.getReadyTimeSec()))
172                 .sorted(comparing(DelayedIdEntry::getReadyTimeSec))
173                 .collect(toCollection(ArrayList::new));
174
175         releasedIdHolder.replaceDelayedEntries(delayedIdEntryInCache);
176
177         IdLocalPool idLocalPool = new IdLocalPool(idUtils, idPool.getPoolName());
178         idLocalPool.setAvailableIds(availableIdHolder);
179         idLocalPool.setReleasedIds(releasedIdHolder);
180         localPool.put(parentPoolName, idLocalPool);
181         if (LOG.isDebugEnabled()) {
182             LOG.debug("Populating cache for {} with {}", idLocalPool.getPoolName(), idLocalPool);
183         }
184         return true;
185     }
186
187     @Override
188     public Future<RpcResult<Void>> createIdPool(CreateIdPoolInput input) {
189         LOG.info("createIdPool called with input {}", input);
190         String poolName = input.getPoolName();
191         long low = input.getLow();
192         long high = input.getHigh();
193         long blockSize = idUtils.computeBlockSize(low, high);
194         Future<RpcResult<Void>> futureResult;
195         try {
196             idUtils.lock(lockManager, poolName);
197             WriteTransaction tx = broker.newWriteOnlyTransaction();
198             poolName = poolName.intern();
199             IdPool idPool;
200             idPool = createGlobalPool(tx, poolName, low, high, blockSize);
201             String localPoolName = idUtils.getLocalPoolName(poolName);
202             IdLocalPool idLocalPool = localPool.get(poolName);
203             if (idLocalPool == null) {
204                 createLocalPool(tx, localPoolName, idPool);
205                 idUtils.updateChildPool(tx, idPool.getPoolName(), localPoolName);
206             }
207             tx.submit().checkedGet();
208             futureResult = RpcResultBuilder.<Void>success().buildFuture();
209         } catch (OperationFailedException | IdManagerException e) {
210             futureResult = buildFailedRpcResultFuture("createIdPool failed: " + input.toString(), e);
211         } finally {
212             idUtils.unlock(lockManager, poolName);
213         }
214         return futureResult;
215     }
216
217     @Override
218     public Future<RpcResult<AllocateIdOutput>> allocateId(AllocateIdInput input) {
219         LOG.debug("AllocateId called with input {}", input);
220         String idKey = input.getIdKey();
221         String poolName = input.getPoolName();
222         String localPoolName = idUtils.getLocalPoolName(poolName);
223         long newIdValue = -1;
224         AllocateIdOutputBuilder output = new AllocateIdOutputBuilder();
225         Future<RpcResult<AllocateIdOutput>> futureResult;
226         try {
227             //allocateIdFromLocalPool method returns a list of IDs with one element. This element is obtained by get(0)
228             newIdValue = allocateIdFromLocalPool(poolName, localPoolName, idKey, 1).get(0);
229             output.setIdValue(newIdValue);
230             futureResult = RpcResultBuilder.<AllocateIdOutput>success().withResult(output.build()).buildFuture();
231         } catch (OperationFailedException | IdManagerException e) {
232             completeExceptionallyIfPresent(poolName, idKey, e);
233             futureResult = buildFailedRpcResultFuture("allocateId failed: " + input.toString(), e);
234         }
235         return futureResult;
236     }
237
238     private void completeExceptionallyIfPresent(String poolName, String idKey, Exception exception) {
239         CompletableFuture<List<Long>> completableFuture =
240                 idUtils.removeAllocatedIds(idUtils.getUniqueKey(poolName, idKey));
241         if (completableFuture != null) {
242             completableFuture.completeExceptionally(exception);
243         }
244     }
245
246     @Override
247     public Future<RpcResult<AllocateIdRangeOutput>> allocateIdRange(AllocateIdRangeInput input) {
248         if (LOG.isDebugEnabled()) {
249             LOG.debug("AllocateIdRange called with input {}", input);
250         }
251         String idKey = input.getIdKey();
252         String poolName = input.getPoolName();
253         long size = input.getSize();
254         String localPoolName = idUtils.getLocalPoolName(poolName);
255         List<Long> newIdValuesList = new ArrayList<>();
256         AllocateIdRangeOutputBuilder output = new AllocateIdRangeOutputBuilder();
257         Future<RpcResult<AllocateIdRangeOutput>> futureResult;
258         try {
259             newIdValuesList = allocateIdFromLocalPool(poolName, localPoolName, idKey, size);
260             Collections.sort(newIdValuesList);
261             output.setIdValues(newIdValuesList);
262             futureResult = RpcResultBuilder.<AllocateIdRangeOutput>success().withResult(output.build()).buildFuture();
263         } catch (OperationFailedException | IdManagerException e) {
264             completeExceptionallyIfPresent(poolName, idKey, e);
265             futureResult = buildFailedRpcResultFuture("allocateIdRange failed: " + input.toString(), e);
266         }
267         return futureResult;
268     }
269
270     @Override
271     public Future<RpcResult<Void>> deleteIdPool(DeleteIdPoolInput input) {
272         if (LOG.isDebugEnabled()) {
273             LOG.debug("DeleteIdPool called with input {}", input);
274         }
275         String poolName = input.getPoolName();
276         Future<RpcResult<Void>> futureResult;
277         try {
278             InstanceIdentifier<IdPool> idPoolToBeDeleted = idUtils.getIdPoolInstance(poolName);
279             poolName = poolName.intern();
280             synchronized (poolName) {
281                 IdPool idPool = singleTxDB.syncRead(CONFIGURATION, idPoolToBeDeleted);
282                 List<ChildPools> childPoolList = idPool.getChildPools();
283                 if (childPoolList != null) {
284                     childPoolList.stream().forEach(childPool -> deletePool(childPool.getChildPoolName()));
285                 }
286                 singleTxDB.syncDelete(CONFIGURATION, idPoolToBeDeleted);
287                 if (LOG.isDebugEnabled()) {
288                     LOG.debug("Deleted id pool {}", poolName);
289                 }
290             }
291             futureResult = RpcResultBuilder.<Void>success().buildFuture();
292         } catch (OperationFailedException e) {
293             futureResult = buildFailedRpcResultFuture("deleteIdPool failed: " + input.toString(), e);
294         }
295         return futureResult;
296     }
297
298     @Override
299     public Future<RpcResult<Void>> releaseId(ReleaseIdInput input) {
300         String poolName = input.getPoolName();
301         String idKey = input.getIdKey();
302         LOG.info("Releasing ID {} from pool {}", idKey, poolName);
303         Future<RpcResult<Void>> futureResult;
304         String uniqueKey = idUtils.getUniqueKey(poolName, idKey);
305         try {
306             idUtils.lock(lockManager, uniqueKey);
307             releaseIdFromLocalPool(poolName, idUtils.getLocalPoolName(poolName), idKey);
308             futureResult = RpcResultBuilder.<Void>success().buildFuture();
309         } catch (ReadFailedException | IdManagerException e) {
310             futureResult = buildFailedRpcResultFuture("releaseId failed: " + input.toString(), e);
311             idUtils.unlock(lockManager, uniqueKey);
312         }
313         return futureResult;
314     }
315
316     private <T> ListenableFuture<RpcResult<T>> buildFailedRpcResultFuture(String msg, Exception exception) {
317         if (exception instanceof IdDoesNotExistException) {
318             // Do not log full stack trace in case ID does not exist
319             LOG.error(msg, exception);
320         } else {
321             LOG.error(msg, exception);
322         }
323         RpcResultBuilder<T> failedRpcResultBuilder = RpcResultBuilder.failed();
324         failedRpcResultBuilder.withError(ErrorType.APPLICATION, msg, exception);
325         if (exception instanceof OperationFailedException) {
326             failedRpcResultBuilder.withRpcErrors(((OperationFailedException) exception).getErrorList());
327         }
328         return failedRpcResultBuilder.buildFuture();
329     }
330
331     private List<Long> allocateIdFromLocalPool(String parentPoolName, String localPoolName,
332             String idKey, long size) throws OperationFailedException, IdManagerException {
333         LOG.debug("Allocating id from local pool {}. Parent pool {}. Idkey {}", localPoolName, parentPoolName, idKey);
334         String uniqueIdKey = idUtils.getUniqueKey(parentPoolName, idKey);
335         CompletableFuture<List<Long>> futureIdValues = new CompletableFuture<>();
336         CompletableFuture<List<Long>> existingFutureIdValue =
337                 idUtils.putAllocatedIdsIfAbsent(uniqueIdKey, futureIdValues);
338         if (existingFutureIdValue != null) {
339             try {
340                 return existingFutureIdValue.get();
341             } catch (InterruptedException | ExecutionException e) {
342                 LOG.warn("Could not obtain id from existing futureIdValue for idKey {} and pool {}.",
343                         idKey, parentPoolName);
344                 throw new IdManagerException(e.getMessage(), e);
345             }
346         }
347         try {
348             List<Long> newIdValuesList = checkForIdInIdEntries(parentPoolName, idKey, uniqueIdKey, futureIdValues,
349                     existingFutureIdValue);
350             if (!newIdValuesList.isEmpty()) {
351                 return newIdValuesList;
352             }
353             //This get will not help in concurrent reads. Hence the same read needs to be done again.
354             IdLocalPool localIdPool = getOrCreateLocalIdPool(parentPoolName, localPoolName);
355             LOG.info("Got pool {}", localIdPool);
356             long newIdValue = -1;
357             localPoolName = localPoolName.intern();
358             if (size == 1) {
359                 newIdValue = getIdFromLocalPoolCache(localIdPool, parentPoolName);
360                 newIdValuesList.add(newIdValue);
361             } else {
362                 getRangeOfIds(parentPoolName, localPoolName, size, newIdValuesList, localIdPool, newIdValue);
363             }
364             LOG.info("The newIdValues {} for the idKey {}", newIdValuesList, idKey);
365             idUtils.putReleaseIdLatch(uniqueIdKey, new CountDownLatch(1));
366             UpdateIdEntryJob job = new UpdateIdEntryJob(parentPoolName, localPoolName, idKey, newIdValuesList, broker,
367                     idUtils, lockManager);
368             jobCoordinator.enqueueJob(parentPoolName, job, IdUtils.RETRY_COUNT);
369             futureIdValues.complete(newIdValuesList);
370             return newIdValuesList;
371         } catch (OperationFailedException | IdManagerException e) {
372             idUtils.unlock(lockManager, uniqueIdKey);
373             throw e;
374         }
375     }
376
377     private Long getIdFromLocalPoolCache(IdLocalPool localIdPool, String parentPoolName)
378             throws OperationFailedException, IdManagerException {
379         while (true) {
380             IdHolder releasedIds = localIdPool.getReleasedIds();
381             Optional<Long> releasedId = Optional.absent();
382             releasedId = releasedIds.allocateId();
383             if (releasedId.isPresent()) {
384                 IdHolderSyncJob poolSyncJob =
385                         new IdHolderSyncJob(localIdPool.getPoolName(), localIdPool.getReleasedIds(), broker,
386                                 idUtils);
387                 jobCoordinator.enqueueJob(localIdPool.getPoolName(), poolSyncJob, IdUtils.RETRY_COUNT);
388                 return releasedId.get();
389             }
390             Optional<Long> availableId = Optional.absent();
391             IdHolder availableIds = localIdPool.getAvailableIds();
392             if (availableIds != null) {
393                 availableId = availableIds.allocateId();
394                 if (availableId.isPresent()) {
395                     IdHolderSyncJob poolSyncJob =
396                             new IdHolderSyncJob(localIdPool.getPoolName(), localIdPool.getAvailableIds(),
397                                     broker, idUtils);
398                     jobCoordinator.enqueueJob(localIdPool.getPoolName(), poolSyncJob, IdUtils.RETRY_COUNT);
399                     return availableId.get();
400                 }
401             }
402             long idCount = getIdBlockFromParentPool(parentPoolName, localIdPool);
403             if (idCount <= 0) {
404                 if (LOG.isDebugEnabled()) {
405                     LOG.debug("Unable to allocate Id block from global pool {}", parentPoolName);
406                 }
407                 throw new IdManagerException(String.format("Ids exhausted for pool : %s", parentPoolName));
408             }
409         }
410     }
411
412     /**
413      * Changes made to availableIds and releasedIds will not be persisted to the datastore.
414      */
415     private long getIdBlockFromParentPool(String parentPoolName, IdLocalPool localIdPool)
416             throws OperationFailedException, IdManagerException {
417         if (LOG.isDebugEnabled()) {
418             LOG.debug("Allocating block of id from parent pool {}", parentPoolName);
419         }
420         InstanceIdentifier<IdPool> idPoolInstanceIdentifier = idUtils.getIdPoolInstance(parentPoolName);
421         parentPoolName = parentPoolName.intern();
422         idUtils.lock(lockManager, parentPoolName);
423         long idCount = 0;
424         try {
425             // Check if the childpool already got id block.
426             long availableIdCount =
427                     localIdPool.getAvailableIds().getAvailableIdCount()
428                             + localIdPool.getReleasedIds().getAvailableIdCount();
429             if (availableIdCount > 0) {
430                 return availableIdCount;
431             }
432             WriteTransaction tx = broker.newWriteOnlyTransaction();
433             IdPool parentIdPool = singleTxDB.syncRead(CONFIGURATION, idPoolInstanceIdentifier);
434             idCount = allocateIdBlockFromParentPool(localIdPool, parentIdPool, tx);
435             tx.submit().checkedGet();
436         } catch (IdManagerException | NullPointerException e) {
437             LOG.error("Error getting id block from parent pool", e);
438         } finally {
439             idUtils.unlock(lockManager, parentPoolName);
440         }
441         return idCount;
442     }
443
444     private long allocateIdBlockFromParentPool(IdLocalPool localPoolCache, IdPool parentIdPool, WriteTransaction tx)
445             throws OperationFailedException, IdManagerException {
446         long idCount = -1;
447         ReleasedIdsHolderBuilder releasedIdsBuilderParent = idUtils.getReleaseIdsHolderBuilder(parentIdPool);
448         while (true) {
449             idCount = allocateIdBlockFromReleasedIdsHolder(localPoolCache, releasedIdsBuilderParent, parentIdPool, tx);
450             if (idCount > 0) {
451                 return idCount;
452             }
453             idCount = allocateIdBlockFromAvailableIdsHolder(localPoolCache, parentIdPool, tx);
454             if (idCount > 0) {
455                 return idCount;
456             }
457             idCount = getIdsFromOtherChildPools(releasedIdsBuilderParent, parentIdPool);
458             if (idCount <= 0) {
459                 if (LOG.isDebugEnabled()) {
460                     LOG.debug("Unable to allocate Id block from global pool");
461                 }
462                 throw new IdManagerException(String.format("Ids exhausted for pool : %s", parentIdPool.getPoolName()));
463             }
464         }
465     }
466
467     private long getIdsFromOtherChildPools(ReleasedIdsHolderBuilder releasedIdsBuilderParent, IdPool parentIdPool)
468             throws OperationFailedException {
469         List<ChildPools> childPoolsList = parentIdPool.getChildPools();
470         // Sorting the child pools on last accessed time so that the pool that
471         // was not accessed for a long time comes first.
472         Collections.sort(childPoolsList, comparing(ChildPools::getLastAccessTime));
473         long currentTime = System.currentTimeMillis() / 1000;
474         for (ChildPools childPools : childPoolsList) {
475             if (childPools.getLastAccessTime() + DEFAULT_IDLE_TIME > currentTime) {
476                 break;
477             }
478             if (!childPools.getChildPoolName().equals(idUtils.getLocalPoolName(parentIdPool.getPoolName()))) {
479                 InstanceIdentifier<IdPool> idPoolInstanceIdentifier = idUtils
480                         .getIdPoolInstance(childPools.getChildPoolName());
481                 IdPool otherChildPool = singleTxDB.syncRead(CONFIGURATION, idPoolInstanceIdentifier);
482                 ReleasedIdsHolderBuilder releasedIds = idUtils.getReleaseIdsHolderBuilder(otherChildPool);
483
484                 List<DelayedIdEntries> delayedIdEntriesChild = releasedIds.getDelayedIdEntries();
485                 List<DelayedIdEntries> delayedIdEntriesParent = releasedIdsBuilderParent.getDelayedIdEntries();
486                 if (delayedIdEntriesParent == null) {
487                     delayedIdEntriesParent = new LinkedList<>();
488                 }
489                 delayedIdEntriesParent.addAll(delayedIdEntriesChild);
490                 delayedIdEntriesChild.removeAll(delayedIdEntriesChild);
491
492                 AvailableIdsHolderBuilder availableIds = idUtils.getAvailableIdsHolderBuilder(otherChildPool);
493                 while (idUtils.isIdAvailable(availableIds)) {
494                     long cursor = availableIds.getCursor() + 1;
495                     delayedIdEntriesParent.add(idUtils.createDelayedIdEntry(cursor, currentTime));
496                     availableIds.setCursor(cursor);
497                 }
498
499                 long totalAvailableIdCount = releasedIds.getDelayedIdEntries().size()
500                         + idUtils.getAvailableIdsCount(availableIds);
501                 long count = releasedIdsBuilderParent.getAvailableIdCount() + totalAvailableIdCount;
502                 releasedIdsBuilderParent.setDelayedIdEntries(delayedIdEntriesParent).setAvailableIdCount(count);
503                 singleTxDB.syncUpdate(CONFIGURATION, idPoolInstanceIdentifier,
504                         new IdPoolBuilder().setKey(new IdPoolKey(otherChildPool.getPoolName()))
505                                 .setAvailableIdsHolder(availableIds.build()).setReleasedIdsHolder(releasedIds.build())
506                                 .build());
507                 return totalAvailableIdCount;
508             }
509         }
510         return 0;
511     }
512
513     private long allocateIdBlockFromReleasedIdsHolder(IdLocalPool localIdPool,
514             ReleasedIdsHolderBuilder releasedIdsBuilderParent, IdPool parentIdPool, WriteTransaction tx) {
515         if (releasedIdsBuilderParent.getAvailableIdCount() == 0) {
516             LOG.debug("Ids unavailable in releasedIds of parent pool {}", parentIdPool);
517             return 0;
518         }
519         List<DelayedIdEntries> delayedIdEntriesParent = releasedIdsBuilderParent.getDelayedIdEntries();
520         int idCount = Math.min(delayedIdEntriesParent.size(), parentIdPool.getBlockSize());
521         List<DelayedIdEntries> idEntriesToBeRemoved = delayedIdEntriesParent.subList(0, idCount);
522         ReleasedIdHolder releasedIds = (ReleasedIdHolder) localIdPool.getReleasedIds();
523         List<DelayedIdEntry> delayedIdEntriesLocalCache = releasedIds.getDelayedEntries();
524         List<DelayedIdEntry> delayedIdEntriesFromParentPool = idEntriesToBeRemoved
525                 .stream()
526                 .map(delayedIdEntry -> new DelayedIdEntry(delayedIdEntry
527                         .getId(), delayedIdEntry.getReadyTimeSec()))
528                 .sorted(comparing(DelayedIdEntry::getReadyTimeSec))
529                 .collect(toCollection(ArrayList::new));
530         delayedIdEntriesFromParentPool.addAll(delayedIdEntriesLocalCache);
531         releasedIds.replaceDelayedEntries(delayedIdEntriesFromParentPool);
532         releasedIds.setAvailableIdCount(releasedIds.getAvailableIdCount() + idCount);
533         localIdPool.setReleasedIds(releasedIds);
534         delayedIdEntriesParent.removeAll(idEntriesToBeRemoved);
535         releasedIdsBuilderParent.setDelayedIdEntries(delayedIdEntriesParent);
536         InstanceIdentifier<ReleasedIdsHolder> releasedIdsHolderInstanceIdentifier = InstanceIdentifier
537                 .builder(IdPools.class).child(IdPool.class,
538                         new IdPoolKey(parentIdPool.getPoolName())).child(ReleasedIdsHolder.class).build();
539         releasedIdsBuilderParent.setAvailableIdCount(releasedIdsBuilderParent.getAvailableIdCount() - idCount);
540         LOG.debug("Allocated {} ids from releasedIds of parent pool {}", idCount, parentIdPool);
541         tx.merge(CONFIGURATION, releasedIdsHolderInstanceIdentifier,
542                 releasedIdsBuilderParent.build(), true);
543         return idCount;
544     }
545
546     private long allocateIdBlockFromAvailableIdsHolder(IdLocalPool localIdPool, IdPool parentIdPool,
547             WriteTransaction tx) {
548         long idCount = 0;
549         AvailableIdsHolderBuilder availableIdsBuilderParent = idUtils.getAvailableIdsHolderBuilder(parentIdPool);
550         long end = availableIdsBuilderParent.getEnd();
551         long cur = availableIdsBuilderParent.getCursor();
552         if (!idUtils.isIdAvailable(availableIdsBuilderParent)) {
553             if (LOG.isDebugEnabled()) {
554                 LOG.debug("Ids exhausted in parent pool {}", parentIdPool);
555             }
556             return idCount;
557         }
558         // Update availableIdsHolder of Local Pool
559         idCount = Math.min(end - cur, parentIdPool.getBlockSize());
560         AvailableIdHolder availableIds = new AvailableIdHolder(idUtils, cur + 1, cur + idCount);
561         localIdPool.setAvailableIds(availableIds);
562         // Update availableIdsHolder of Global Pool
563         InstanceIdentifier<AvailableIdsHolder> availableIdsHolderInstanceIdentifier = InstanceIdentifier
564                 .builder(IdPools.class).child(IdPool.class,
565                         new IdPoolKey(parentIdPool.getPoolName())).child(AvailableIdsHolder.class).build();
566         availableIdsBuilderParent.setCursor(cur + idCount);
567         if (LOG.isDebugEnabled()) {
568             LOG.debug("Allocated {} ids from availableIds of global pool {}", idCount, parentIdPool);
569         }
570         tx.merge(CONFIGURATION, availableIdsHolderInstanceIdentifier,
571                 availableIdsBuilderParent.build(), true);
572         return idCount;
573     }
574
575     private void releaseIdFromLocalPool(String parentPoolName, String localPoolName, String idKey)
576             throws ReadFailedException, IdManagerException {
577         String idLatchKey = idUtils.getUniqueKey(parentPoolName, idKey);
578         LOG.debug("Releasing ID {} from pool {}", idKey, localPoolName);
579         CountDownLatch latch = idUtils.getReleaseIdLatch(idLatchKey);
580         if (latch != null) {
581             try {
582                 latch.await(10, TimeUnit.SECONDS);
583             } catch (InterruptedException ignored) {
584                 LOG.warn("Thread interrupted while releasing id {} from id pool {}", idKey, parentPoolName);
585             } finally {
586                 idUtils.removeReleaseIdLatch(idLatchKey);
587             }
588         }
589         localPoolName = localPoolName.intern();
590         InstanceIdentifier<IdPool> parentIdPoolInstanceIdentifier = idUtils.getIdPoolInstance(parentPoolName);
591         IdPool parentIdPool = singleTxDB.syncRead(CONFIGURATION, parentIdPoolInstanceIdentifier);
592         List<IdEntries> idEntries = parentIdPool.getIdEntries();
593         List<IdEntries> newIdEntries = idEntries;
594         if (idEntries == null) {
595             throw new IdDoesNotExistException(parentPoolName, idKey);
596         }
597         InstanceIdentifier<IdEntries> existingId = idUtils.getIdEntry(parentIdPoolInstanceIdentifier, idKey);
598         Optional<IdEntries> existingIdEntryObject = singleTxDB.syncReadOptional(CONFIGURATION, existingId);
599         if (!existingIdEntryObject.isPresent()) {
600             LOG.info("Specified Id key {} does not exist in id pool {}", idKey, parentPoolName);
601             idUtils.unlock(lockManager, idLatchKey);
602             return;
603         }
604         IdEntries existingIdEntry = existingIdEntryObject.get();
605         List<Long> idValuesList = existingIdEntry.getIdValue();
606         IdLocalPool localIdPoolCache = localPool.get(parentPoolName);
607         boolean isRemoved = newIdEntries.remove(existingIdEntry);
608         LOG.debug("The entry {} is removed {}", existingIdEntry, isRemoved);
609         updateDelayedEntriesInLocalCache(idValuesList, parentPoolName, localIdPoolCache);
610         IdHolderSyncJob poolSyncJob = new IdHolderSyncJob(localPoolName, localIdPoolCache.getReleasedIds(), broker,
611                 idUtils);
612         jobCoordinator.enqueueJob(localPoolName, poolSyncJob, IdUtils.RETRY_COUNT);
613         scheduleCleanUpTask(localIdPoolCache, parentPoolName, parentIdPool.getBlockSize());
614         LOG.debug("Released id ({}, {}) from pool {}", idKey, idValuesList, localPoolName);
615         // Updating id entries in the parent pool. This will be used for restart scenario
616         UpdateIdEntryJob job = new UpdateIdEntryJob(parentPoolName, localPoolName, idKey, null, broker, idUtils,
617                         lockManager);
618         jobCoordinator.enqueueJob(parentPoolName, job, IdUtils.RETRY_COUNT);
619     }
620
621     private void scheduleCleanUpTask(final IdLocalPool localIdPoolCache,
622             final String parentPoolName, final int blockSize) {
623         TimerTask scheduledTask = new TimerTask() {
624             @Override
625             public void run() {
626                 CleanUpJob job = new CleanUpJob(localIdPoolCache, broker, parentPoolName, blockSize, lockManager,
627                         idUtils, jobCoordinator);
628                 jobCoordinator.enqueueJob(localIdPoolCache.getPoolName(), job, IdUtils.RETRY_COUNT);
629             }
630         };
631         cleanJobTimer.schedule(scheduledTask, IdUtils.DEFAULT_DELAY_TIME * 1000);
632     }
633
634     private IdPool createGlobalPool(WriteTransaction tx, String poolName, long low, long high, long blockSize)
635             throws ReadFailedException {
636         IdPool idPool;
637         InstanceIdentifier<IdPool> idPoolInstanceIdentifier = idUtils.getIdPoolInstance(poolName);
638         Optional<IdPool> existingIdPool = singleTxDB.syncReadOptional(CONFIGURATION, idPoolInstanceIdentifier);
639         if (!existingIdPool.isPresent()) {
640             if (LOG.isDebugEnabled()) {
641                 LOG.debug("Creating new global pool {}", poolName);
642             }
643             idPool = idUtils.createGlobalPool(poolName, low, high, blockSize);
644             tx.put(CONFIGURATION, idPoolInstanceIdentifier, idPool, true);
645         } else {
646             idPool = existingIdPool.get();
647             if (LOG.isDebugEnabled()) {
648                 LOG.debug("GlobalPool exists {}", idPool);
649             }
650         }
651         return idPool;
652     }
653
654     private IdLocalPool createLocalPool(WriteTransaction tx, String localPoolName, IdPool idPool)
655             throws OperationFailedException, IdManagerException {
656         localPoolName = localPoolName.intern();
657         IdLocalPool idLocalPool = new IdLocalPool(idUtils, localPoolName);
658         allocateIdBlockFromParentPool(idLocalPool, idPool, tx);
659         String parentPool = idPool.getPoolName();
660         localPool.put(parentPool, idLocalPool);
661         LocalPoolCreateJob job = new LocalPoolCreateJob(idLocalPool, broker, idPool.getPoolName(),
662                 idPool.getBlockSize(), idUtils);
663         jobCoordinator.enqueueJob(localPoolName, job, IdUtils.RETRY_COUNT);
664         return idLocalPool;
665     }
666
667     private void deletePool(String poolName) {
668         LocalPoolDeleteJob job = new LocalPoolDeleteJob(poolName, broker, idUtils);
669         jobCoordinator.enqueueJob(poolName, job, IdUtils.RETRY_COUNT);
670     }
671
672     public void poolDeleted(String parentPoolName, String poolName) {
673         IdLocalPool idLocalPool = localPool.get(parentPoolName);
674         if (idLocalPool != null) {
675             if (idLocalPool.getPoolName().equals(poolName)) {
676                 localPool.remove(parentPoolName);
677             }
678         }
679     }
680
681     private void updateDelayedEntriesInLocalCache(List<Long> idsList, String parentPoolName,
682             IdLocalPool localPoolCache) {
683         for (long idValue : idsList) {
684             localPoolCache.getReleasedIds().addId(idValue);
685         }
686         localPool.put(parentPoolName, localPoolCache);
687     }
688
689     public java.util.Optional<IdLocalPool> getIdLocalPool(String parentPoolName) {
690         return java.util.Optional.ofNullable(localPool.get(parentPoolName)).map(IdLocalPool::deepCopyOf);
691     }
692
693     private List<Long> checkForIdInIdEntries(String parentPoolName, String idKey, String uniqueIdKey,
694             CompletableFuture<List<Long>> futureIdValues, CompletableFuture<List<Long>> existingFutureIdValue)
695             throws IdManagerException, ReadFailedException {
696         InstanceIdentifier<IdPool> parentIdPoolInstanceIdentifier = idUtils.getIdPoolInstance(parentPoolName);
697         InstanceIdentifier<IdEntries> existingId = idUtils.getIdEntry(parentIdPoolInstanceIdentifier, idKey);
698         idUtils.lock(lockManager, uniqueIdKey);
699         List<Long> newIdValuesList = new ArrayList<>();
700         Optional<IdEntries> existingIdEntry = singleTxDB.syncReadOptional(CONFIGURATION, existingId);
701         if (existingIdEntry.isPresent()) {
702             newIdValuesList = existingIdEntry.get().getIdValue();
703             LOG.debug("Existing ids {} for the key {} ", newIdValuesList, idKey);
704             // Inform other waiting threads about this new value.
705             futureIdValues.complete(newIdValuesList);
706             // This is to avoid stale entries in the map. If this thread had populated the map,
707             // then the entry should be removed.
708             if (existingFutureIdValue == null) {
709                 idUtils.removeAllocatedIds(uniqueIdKey);
710             }
711             idUtils.unlock(lockManager, uniqueIdKey);
712             return newIdValuesList;
713         }
714         return newIdValuesList;
715     }
716
717     private IdLocalPool getOrCreateLocalIdPool(String parentPoolName, String localPoolName)
718             throws IdManagerException, ReadFailedException, OperationFailedException, TransactionCommitFailedException {
719         IdLocalPool localIdPool = localPool.get(parentPoolName);
720         if (localIdPool == null) {
721             idUtils.lock(lockManager, parentPoolName);
722             try {
723                 // Check if a previous thread that got the cluster-wide lock
724                 // first, has created the localPool
725                 if (localPool.get(parentPoolName) == null) {
726                     WriteTransaction tx = broker.newWriteOnlyTransaction();
727                     InstanceIdentifier<IdPool> parentIdPoolInstanceIdentifier = idUtils
728                             .getIdPoolInstance(parentPoolName);
729                     IdPool parentIdPool = singleTxDB.syncRead(CONFIGURATION, parentIdPoolInstanceIdentifier);
730                     // Return localIdPool
731                     localIdPool = createLocalPool(tx, localPoolName, parentIdPool);
732                     tx.submit().checkedGet();
733                 } else {
734                     localIdPool = localPool.get(parentPoolName);
735                 }
736             } finally {
737                 idUtils.unlock(lockManager, parentPoolName);
738             }
739         }
740         return localIdPool;
741     }
742
743     private void getRangeOfIds(String parentPoolName, String localPoolName, long size, List<Long> newIdValuesList,
744             IdLocalPool localIdPool, long newIdValue) throws ReadFailedException, IdManagerException {
745         InstanceIdentifier<IdPool> parentIdPoolInstanceIdentifier1 = idUtils.getIdPoolInstance(parentPoolName);
746         IdPool parentIdPool = singleTxDB.syncRead(CONFIGURATION, parentIdPoolInstanceIdentifier1);
747         long totalAvailableIdCount = localIdPool.getAvailableIds().getAvailableIdCount()
748                 + localIdPool.getReleasedIds().getAvailableIdCount();
749         AvailableIdsHolderBuilder availableParentIds = idUtils.getAvailableIdsHolderBuilder(parentIdPool);
750         ReleasedIdsHolderBuilder releasedParentIds = idUtils.getReleaseIdsHolderBuilder(parentIdPool);
751         totalAvailableIdCount = totalAvailableIdCount + releasedParentIds.getAvailableIdCount()
752                 + idUtils.getAvailableIdsCount(availableParentIds);
753         if (totalAvailableIdCount > size) {
754             while (size > 0) {
755                 try {
756                     newIdValue = getIdFromLocalPoolCache(localIdPool, parentPoolName);
757                 } catch (OperationFailedException e) {
758                     if (LOG.isDebugEnabled()) {
759                         LOG.debug("Releasing IDs to pool {}", localPoolName);
760                     }
761                     // Releasing the IDs added in newIdValuesList since
762                     // a null list would be returned now, as the
763                     // requested size of list IDs exceeds the number of
764                     // available IDs.
765                     updateDelayedEntriesInLocalCache(newIdValuesList, parentPoolName, localIdPool);
766                 }
767                 newIdValuesList.add(newIdValue);
768                 size--;
769             }
770         } else {
771             throw new IdManagerException(String.format("Ids exhausted for pool : %s", parentPoolName));
772         }
773     }
774 }