blob: 911f577c95d4d27f05683e9454b903ad09d625c2 [file] [log] [blame]
Jonathan Hart612651f2019-11-25 09:21:43 -08001/*
2 * Copyright 2020-present Open Networking Foundation
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
17package org.opencord.aaa.impl;
18
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -070019import com.google.common.collect.ImmutableMap;
Jonathan Hart612651f2019-11-25 09:21:43 -080020import com.google.common.collect.Maps;
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -070021import org.apache.commons.lang3.tuple.Pair;
22import org.slf4j.Logger;
Jonathan Hart612651f2019-11-25 09:21:43 -080023
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -070024import java.util.Iterator;
25import java.util.Map;
Jonathan Hart612651f2019-11-25 09:21:43 -080026import java.util.concurrent.BlockingQueue;
27import java.util.concurrent.ConcurrentMap;
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -070028import java.util.concurrent.Executors;
Jonathan Hart612651f2019-11-25 09:21:43 -080029import java.util.concurrent.LinkedBlockingQueue;
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -070030import java.util.concurrent.ScheduledExecutorService;
31import java.util.concurrent.ScheduledFuture;
32import java.util.concurrent.TimeUnit;
33
34import static org.onlab.util.Tools.groupedThreads;
35import static org.slf4j.LoggerFactory.getLogger;
Jonathan Hart612651f2019-11-25 09:21:43 -080036
37/**
38 * Manages allocating request identifiers and mapping them to sessions.
39 */
40public class IdentifierManager {
41
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -070042 private final Logger log = getLogger(getClass());
43
Jonathan Hart612651f2019-11-25 09:21:43 -080044 private static final int MAX_IDENTIFIER = 256;
45
46 private BlockingQueue<Integer> freeIdNumbers;
47
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -070048 private ConcurrentMap<RequestIdentifier, Pair<String, Long>> idToSession;
49
50 ScheduledFuture<?> scheduledidentifierPruner;
51
52 // TODO read from component config
53 protected static int timeout = 10000;
54 protected static int pollTimeout = 2;
55 protected static int pruningInterval = 3;
Jonathan Hart612651f2019-11-25 09:21:43 -080056
57 /**
58 * Creates and initializes a new identifier manager.
59 */
60 public IdentifierManager() {
61 idToSession = Maps.newConcurrentMap();
62 freeIdNumbers = new LinkedBlockingQueue<>();
63
64 // Starts at 2 because ids 0 and 1 are reserved for RADIUS server status requests.
65 for (int i = 2; i < MAX_IDENTIFIER; i++) {
66 freeIdNumbers.add(i);
67 }
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -070068
69 ScheduledExecutorService identifierPruner = Executors.newSingleThreadScheduledExecutor(
70 groupedThreads("onos/aaa", "idpruner-%d", log));
71
72 scheduledidentifierPruner = identifierPruner.scheduleAtFixedRate(
73 new IdentifierPruner(), 0,
74 pruningInterval, TimeUnit.SECONDS);
Jonathan Hart612651f2019-11-25 09:21:43 -080075 }
76
77 /**
78 * Gets a new identifier and maps it to the given session ID.
79 *
80 * @param sessionId session this identifier is associated with
81 * @return identifier
82 */
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -070083 public RequestIdentifier getNewIdentifier(String sessionId) {
84 // Run this part without the lock.
85 Integer idNum;
Jonathan Hart612651f2019-11-25 09:21:43 -080086 try {
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -070087 idNum = freeIdNumbers.poll(pollTimeout, TimeUnit.SECONDS);
Jonathan Hart612651f2019-11-25 09:21:43 -080088 } catch (InterruptedException e) {
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -070089 // Interrupted case
90 if (log.isTraceEnabled()) {
91 log.trace("Interrupted while waiting for an id");
92 }
Jonathan Hart612651f2019-11-25 09:21:43 -080093 return null;
94 }
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -070095 // Timeout let's exit
96 if (idNum == null) {
97 if (log.isTraceEnabled()) {
98 log.trace("Timedout there are no available ids");
99 }
100 return null;
101 }
102 // Start of the synchronized zone. Real contention happens here.
103 // This thread wants to update the session map. The release thread
104 // wants to update the session map first and then free the id. Same
105 // for the pruner. If this thread is interrupted here is not a big issue
106 // its update is not yet visible for the remaining threads: i) the
107 // release thread cannot release an id not yet taken; ii) the pruning
108 // thread cannot prune for the same reason.
109 synchronized (this) {
110 if (log.isTraceEnabled()) {
111 log.trace("Got identifier {} for session {}", idNum, sessionId);
112 }
Jonathan Hart612651f2019-11-25 09:21:43 -0800113
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -0700114 RequestIdentifier id = RequestIdentifier.of((byte) idNum.intValue());
Jonathan Hart612651f2019-11-25 09:21:43 -0800115
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -0700116 idToSession.put(id, Pair.of(sessionId, System.currentTimeMillis()));
Jonathan Hart612651f2019-11-25 09:21:43 -0800117
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -0700118 return id;
119 }
Jonathan Hart612651f2019-11-25 09:21:43 -0800120 }
121
122 /**
123 * Gets the session ID associated with a given request ID.
124 *
125 * @param id request ID
126 * @return session ID
127 */
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -0700128 public synchronized String getSessionId(RequestIdentifier id) {
129 //TODO this has multiple accesses
130 return idToSession.get(id) == null ? null : idToSession.get(id).getKey();
Jonathan Hart612651f2019-11-25 09:21:43 -0800131 }
132
133 /**
134 * Releases a request identifier and removes session mapping.
135 *
136 * @param id request identifier to release
137 */
138 public synchronized void releaseIdentifier(RequestIdentifier id) {
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -0700139 if (log.isTraceEnabled()) {
Matteo Scandolo105210b2020-12-10 10:32:39 -0800140 log.trace("Releasing identifier {}", id.getReadableIdentifier());
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -0700141 }
142
143 Pair<String, Long> session = idToSession.remove(id);
Jonathan Hart612651f2019-11-25 09:21:43 -0800144 if (session == null) {
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -0700145 if (log.isTraceEnabled()) {
Matteo Scandolo105210b2020-12-10 10:32:39 -0800146 log.trace("Unable to released identifier {} for session null", id.getReadableIdentifier());
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -0700147 }
Jonathan Hart612651f2019-11-25 09:21:43 -0800148 // this id wasn't mapped to a session so is still free
149 return;
150 }
151
152 // add id number back to set of free ids
Matteo Scandolo105210b2020-12-10 10:32:39 -0800153 freeIdNumbers.add(id.getReadableIdentifier());
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -0700154
155 if (log.isTraceEnabled()) {
Matteo Scandolo105210b2020-12-10 10:32:39 -0800156 log.trace("Released identifier {} for session {}", id.getReadableIdentifier(), session.getKey());
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -0700157 }
158 }
159
160 /**
161 * Returns true if this ID is currently taken.
162 *
163 * @param id request identifier to check
164 * @return boolean
165 */
166 private boolean isIdentifierTaken(Integer id) {
167 return !freeIdNumbers.contains(id);
168 }
169
170 /**
171 * Returns the count of available identifiers in a given moment.
172 *
173 * @return boolean
174 */
175 public int getAvailableIdentifiers() {
176 return freeIdNumbers.size();
177 }
178
179 private synchronized void pruneIfNeeded() {
180 if (log.isTraceEnabled()) {
181 log.trace("Starting pruning cycle");
182 }
183 // Gets an immutable copy of the ids and release the ones that exceed the timeout
184 Map<RequestIdentifier, Pair<String, Long>> idsToCheck = ImmutableMap.copyOf(idToSession);
185 // We should not modify while iterating - this is why we get a copy
186 Iterator<Map.Entry<RequestIdentifier, Pair<String, Long>>> itr = idsToCheck.entrySet().iterator();
187 itr.forEachRemaining((entry) -> {
188 RequestIdentifier id = entry.getKey();
189 Pair<String, Long> info = entry.getValue();
190 long diff = System.currentTimeMillis() - info.getValue();
191 if (diff >= timeout) {
192 if (log.isTraceEnabled()) {
193 log.trace("Identifier {} for session {} has exceeded timeout {}, releasing",
Matteo Scandolo105210b2020-12-10 10:32:39 -0800194 id.getReadableIdentifier(), info.getKey(), timeout);
Matteo Scandolobbc1ffb2020-10-16 15:56:20 -0700195 }
196 releaseIdentifier(id);
197 }
198 });
199 if (log.isTraceEnabled()) {
200 log.trace("End pruning cycle");
201 }
202 }
203
204 private class IdentifierPruner implements Runnable {
205 @Override
206 public void run() {
207 pruneIfNeeded();
208 }
209
Jonathan Hart612651f2019-11-25 09:21:43 -0800210 }
211}