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