blob: 161673410f42065c539ad92cec502b59d0541c54 [file] [log] [blame]
Esin Karaman971fb7f2017-12-28 13:44:52 +00001/*
2 * Copyright 2018-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 */
17
18package org.onosproject.drivers.bmv2.ctl;
19
20import com.google.common.cache.CacheBuilder;
21import com.google.common.cache.CacheLoader;
22import com.google.common.cache.LoadingCache;
23import com.google.common.collect.Maps;
24import org.apache.commons.lang3.tuple.Pair;
Esin Karaman971fb7f2017-12-28 13:44:52 +000025import org.apache.thrift.protocol.TBinaryProtocol;
26import org.apache.thrift.protocol.TMultiplexedProtocol;
27import org.apache.thrift.protocol.TProtocol;
28import org.apache.thrift.transport.TSocket;
29import org.apache.thrift.transport.TTransport;
30import org.onlab.util.Tools;
31import org.onosproject.bmv2.thriftapi.SimplePreLAG;
32import org.onosproject.cfg.ComponentConfigService;
33import org.onosproject.drivers.bmv2.api.Bmv2DeviceAgent;
34import org.onosproject.drivers.bmv2.api.Bmv2PreController;
35import org.onosproject.net.DeviceId;
36import org.osgi.service.component.ComponentContext;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070037import org.osgi.service.component.annotations.Activate;
38import org.osgi.service.component.annotations.Component;
39import org.osgi.service.component.annotations.Deactivate;
40import org.osgi.service.component.annotations.Modified;
41import org.osgi.service.component.annotations.Reference;
42import org.osgi.service.component.annotations.ReferenceCardinality;
Esin Karaman971fb7f2017-12-28 13:44:52 +000043import org.slf4j.Logger;
44
45import java.util.Dictionary;
46import java.util.Map;
47import java.util.concurrent.TimeUnit;
48import java.util.concurrent.locks.ReadWriteLock;
49import java.util.concurrent.locks.ReentrantReadWriteLock;
50
51import static com.google.common.base.Preconditions.checkNotNull;
52import static java.lang.String.format;
53import static org.slf4j.LoggerFactory.getLogger;
54
55/**
56 * BMv2 PRE controller implementation.
57 */
Ray Milkeyd84f89b2018-08-17 14:54:17 -070058@Component(immediate = true, service = Bmv2PreController.class)
Esin Karaman971fb7f2017-12-28 13:44:52 +000059public class Bmv2PreControllerImpl implements Bmv2PreController {
60
61 private static final int DEVICE_LOCK_CACHE_EXPIRE_TIME_IN_MIN = 10;
62 private static final int DEVICE_LOCK_WAITING_TIME_IN_SEC = 60;
63 private static final int DEFAULT_NUM_CONNECTION_RETRIES = 2;
64 private static final int DEFAULT_TIME_BETWEEN_RETRIES = 10;
65 private static final String THRIFT_SERVICE_NAME = "simple_pre_lag";
66 private final Logger log = getLogger(getClass());
67 private final Map<DeviceId, Pair<TTransport, Bmv2DeviceThriftClient>> clients = Maps.newHashMap();
68 //TODO consider a timeout mechanism for locks to limit the retention time
69 private final LoadingCache<DeviceId, ReadWriteLock> deviceLocks = CacheBuilder.newBuilder()
70 .expireAfterAccess(DEVICE_LOCK_CACHE_EXPIRE_TIME_IN_MIN, TimeUnit.MINUTES)
71 .build(new CacheLoader<DeviceId, ReadWriteLock>() {
72 @Override
73 public ReadWriteLock load(DeviceId deviceId) {
74 return new ReentrantReadWriteLock();
75 }
76 });
Ray Milkeyd84f89b2018-08-17 14:54:17 -070077 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Esin Karaman971fb7f2017-12-28 13:44:52 +000078 protected ComponentConfigService cfgService;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070079 //@Property(name = "numConnectionRetries", intValue = DEFAULT_NUM_CONNECTION_RETRIES,
80 // label = "Number of connection retries after a network error")
Esin Karaman971fb7f2017-12-28 13:44:52 +000081 private int numConnectionRetries = DEFAULT_NUM_CONNECTION_RETRIES;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070082 //@Property(name = "timeBetweenRetries", intValue = DEFAULT_TIME_BETWEEN_RETRIES,
83 // label = "Time between retries in milliseconds")
Esin Karaman971fb7f2017-12-28 13:44:52 +000084 private int timeBetweenRetries = DEFAULT_TIME_BETWEEN_RETRIES;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070085 //@Property(name = "deviceLockWaitingTime", intValue = DEVICE_LOCK_WAITING_TIME_IN_SEC,
86 // label = "Waiting time for a read/write lock in seconds")
Esin Karaman971fb7f2017-12-28 13:44:52 +000087 private int deviceLockWaitingTime = DEVICE_LOCK_WAITING_TIME_IN_SEC;
88
89 @Activate
90 public void activate() {
91 cfgService.registerProperties(getClass());
92 log.info("Started");
93 }
94
95 @Deactivate
96 public void deactivate() {
97 cfgService.unregisterProperties(getClass(), false);
98 log.info("Stopped");
99 }
100
101 @Modified
102 protected void modified(ComponentContext context) {
103 Dictionary<?, ?> properties = context.getProperties();
104
105 Integer newNumConnRetries = Tools.getIntegerProperty(properties, "numConnectionRetries");
106 if (newNumConnRetries != null && newNumConnRetries >= 0) {
107 numConnectionRetries = newNumConnRetries;
108 } else {
109 log.warn("numConnectionRetries must be equal to or greater than 0");
110 }
111
112 Integer newTimeBtwRetries = Tools.getIntegerProperty(properties, "timeBetweenRetries");
113 if (newTimeBtwRetries != null && newTimeBtwRetries >= 0) {
114 timeBetweenRetries = newTimeBtwRetries;
115 } else {
116 log.warn("timeBetweenRetries must be equal to or greater than 0");
117 }
118
119 Integer newDeviceLockWaitingTime = Tools.getIntegerProperty(properties, "deviceLockWaitingTime");
120 if (newDeviceLockWaitingTime != null && newDeviceLockWaitingTime >= 0) {
121 deviceLockWaitingTime = newDeviceLockWaitingTime;
122 } else {
123 log.warn("deviceLockWaitingTime must be equal to or greater than 0");
124 }
125 }
126
127 @Override
128 public boolean createPreClient(DeviceId deviceId, String thriftServerIp, Integer thriftServerPort) {
129 checkNotNull(deviceId);
130 checkNotNull(thriftServerIp);
131 checkNotNull(thriftServerPort);
132
133 if (!acquireWriteLock(deviceId)) {
134 //reason is already logged during the lock acquisition
135 return false;
136 }
137
138 log.info("Creating PRE client for {} through Thrift server {}:{}", deviceId, thriftServerIp, thriftServerPort);
139
140 try {
141 if (clients.containsKey(deviceId)) {
142 throw new IllegalStateException(format("A client already exists for %s", deviceId));
143 } else {
144 return doCreateClient(deviceId, thriftServerIp, thriftServerPort);
145 }
146 } finally {
147 releaseWriteLock(deviceId);
148 }
149 }
150
151 @Override
152 public Bmv2DeviceAgent getPreClient(DeviceId deviceId) {
153 if (!acquireReadLock(deviceId)) {
154 return null;
155 }
156 try {
157 return clients.containsKey(deviceId) ? clients.get(deviceId).getRight() : null;
158 } finally {
159 releaseReadLock(deviceId);
160 }
161 }
162
163 @Override
164 public void removePreClient(DeviceId deviceId) {
165 if (!acquireWriteLock(deviceId)) {
166 //reason is already logged during the lock acquisition
167 return;
168 }
169
170 try {
171 if (clients.containsKey(deviceId)) {
172 TTransport transport = clients.get(deviceId).getLeft();
173 if (transport.isOpen()) {
174 transport.close();
175 }
176 clients.remove(deviceId);
177 }
178 } finally {
179 releaseWriteLock(deviceId);
180 }
181 }
182
183 private boolean acquireWriteLock(DeviceId deviceId) {
184 try {
185 return deviceLocks.getUnchecked(deviceId).writeLock().tryLock(deviceLockWaitingTime, TimeUnit.SECONDS);
186 } catch (InterruptedException e) {
187 log.error("Unable to acquire write lock for device {} due to {}", deviceId, e.toString());
188 }
189 return false;
190 }
191
192 private boolean acquireReadLock(DeviceId deviceId) {
193 try {
194 return deviceLocks.getUnchecked(deviceId).readLock().tryLock(deviceLockWaitingTime, TimeUnit.SECONDS);
195 } catch (InterruptedException e) {
196 log.error("Unable to acquire read lock for device {} due to {}", deviceId, e.toString());
197 }
198 return false;
199 }
200
201 private void releaseWriteLock(DeviceId deviceId) {
202 deviceLocks.getUnchecked(deviceId).writeLock().unlock();
203 }
204
205 private void releaseReadLock(DeviceId deviceId) {
206 deviceLocks.getUnchecked(deviceId).readLock().unlock();
207 }
208
209 private boolean doCreateClient(DeviceId deviceId, String thriftServerIp, Integer thriftServerPort) {
210 SafeThriftClient.Options options = new SafeThriftClient.Options(numConnectionRetries, timeBetweenRetries);
211
212 try {
213 // Make the expensive call
214 TTransport transport = new TSocket(thriftServerIp, thriftServerPort);
215
216 TProtocol protocol = new TBinaryProtocol(transport);
217 // Create a client for simple_pre service.
218 SimplePreLAG.Client simplePreClient = new SimplePreLAG.Client(
219 new TMultiplexedProtocol(protocol, THRIFT_SERVICE_NAME));
220
221 SimplePreLAG.Iface safeSimplePreClient = SafeThriftClient.wrap(simplePreClient,
222 SimplePreLAG.Iface.class,
223 options);
224
225 Bmv2DeviceThriftClient client = new Bmv2DeviceThriftClient(deviceId, safeSimplePreClient);
226 clients.put(deviceId, Pair.of(transport, client));
227 return true;
228
229 } catch (RuntimeException e) {
230 log.warn("Failed to create Thrift client for BMv2 device. deviceId={}, cause={}", deviceId, e);
231 return false;
232 }
233 }
234}