blob: 0e2ea8285c151c479837658e82ec08539116c14c [file] [log] [blame]
Andrea Campanellabc112a92017-06-26 19:06:43 +02001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
Andrea Campanellabc112a92017-06-26 19:06:43 +02003 *
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.onosproject.net.pi.impl;
18
Andrea Campanella48f99fa2017-07-13 19:06:21 +020019import com.google.common.annotations.Beta;
Andrea Campanellabc112a92017-06-26 19:06:43 +020020import com.google.common.collect.ImmutableSet;
Carmelo Casconeda60a612018-08-24 00:01:34 -070021import com.google.common.collect.Sets;
22import com.google.common.util.concurrent.Striped;
Andrea Campanellabc112a92017-06-26 19:06:43 +020023import org.apache.felix.scr.annotations.Activate;
24import org.apache.felix.scr.annotations.Component;
25import org.apache.felix.scr.annotations.Deactivate;
26import org.apache.felix.scr.annotations.Reference;
27import org.apache.felix.scr.annotations.ReferenceCardinality;
28import org.apache.felix.scr.annotations.Service;
29import org.onlab.util.ItemNotFoundException;
30import org.onosproject.net.DeviceId;
31import org.onosproject.net.config.ConfigFactory;
Andrea Campanellabc112a92017-06-26 19:06:43 +020032import org.onosproject.net.config.NetworkConfigRegistry;
33import org.onosproject.net.config.basics.BasicDeviceConfig;
34import org.onosproject.net.config.basics.SubjectFactories;
35import org.onosproject.net.driver.Behaviour;
36import org.onosproject.net.driver.DefaultDriver;
37import org.onosproject.net.driver.Driver;
38import org.onosproject.net.driver.DriverAdminService;
Carmelo Casconeda60a612018-08-24 00:01:34 -070039import org.onosproject.net.driver.DriverEvent;
40import org.onosproject.net.driver.DriverListener;
Andrea Campanellabc112a92017-06-26 19:06:43 +020041import org.onosproject.net.driver.DriverProvider;
Andrea Campanellabc112a92017-06-26 19:06:43 +020042import org.onosproject.net.pi.model.PiPipeconf;
43import org.onosproject.net.pi.model.PiPipeconfId;
Carmelo Cascone39c28ca2017-11-15 13:03:57 -080044import org.onosproject.net.pi.service.PiPipeconfConfig;
45import org.onosproject.net.pi.service.PiPipeconfMappingStore;
46import org.onosproject.net.pi.service.PiPipeconfService;
Andrea Campanellabc112a92017-06-26 19:06:43 +020047import org.slf4j.Logger;
48
49import java.util.HashMap;
50import java.util.Map;
Carmelo Casconeda60a612018-08-24 00:01:34 -070051import java.util.Objects;
Andrea Campanellabc112a92017-06-26 19:06:43 +020052import java.util.Optional;
53import java.util.Set;
Andrea Campanellabc112a92017-06-26 19:06:43 +020054import java.util.concurrent.ConcurrentHashMap;
Carmelo Cascone96beb6f2018-06-27 18:07:12 +020055import java.util.concurrent.ConcurrentMap;
Andrea Campanellabc112a92017-06-26 19:06:43 +020056import java.util.concurrent.ExecutorService;
57import java.util.concurrent.Executors;
Carmelo Casconeda60a612018-08-24 00:01:34 -070058import java.util.concurrent.locks.Lock;
Andrea Campanellabc112a92017-06-26 19:06:43 +020059
Carmelo Cascone44daf562017-07-16 23:55:08 -040060import static java.lang.String.format;
Andrea Campanellabc112a92017-06-26 19:06:43 +020061import static org.onlab.util.Tools.groupedThreads;
62import static org.slf4j.LoggerFactory.getLogger;
63
64
65/**
66 * Implementation of the PiPipeconfService.
67 */
68@Component(immediate = true)
69@Service
Andrea Campanella48f99fa2017-07-13 19:06:21 +020070@Beta
71public class PiPipeconfManager implements PiPipeconfService {
Andrea Campanellabc112a92017-06-26 19:06:43 +020072
73 private final Logger log = getLogger(getClass());
74
Carmelo Cascone158b8c42018-07-04 19:42:37 +020075 private static final String MERGED_DRIVER_SEPARATOR = ":";
Andrea Campanellabc112a92017-06-26 19:06:43 +020076 private static final String CFG_SCHEME = "piPipeconf";
77
78 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
79 protected NetworkConfigRegistry cfgService;
80
81 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Andrea Campanellabc112a92017-06-26 19:06:43 +020082 protected DriverAdminService driverAdminService;
83
Andrea Campanellaf9c409a2017-07-13 14:14:41 +020084 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Carmelo Cascone96beb6f2018-06-27 18:07:12 +020085 private PiPipeconfMappingStore pipeconfMappingStore;
Andrea Campanellaf9c409a2017-07-13 14:14:41 +020086
Carmelo Cascone96beb6f2018-06-27 18:07:12 +020087 // Registered pipeconf are replicated through the app subsystem and
88 // registered on app activated events. Hence, there should be no need of
89 // distributing this map.
90 protected ConcurrentMap<PiPipeconfId, PiPipeconf> pipeconfs = new ConcurrentHashMap<>();
Andrea Campanellabc112a92017-06-26 19:06:43 +020091
Carmelo Casconeda60a612018-08-24 00:01:34 -070092 private final DriverListener driverListener = new InternalDriverListener();
93 private final Set<String> missingMergedDrivers = Sets.newCopyOnWriteArraySet();
94 private final Striped<Lock> locks = Striped.lock(20);
95
Carmelo Cascone96beb6f2018-06-27 18:07:12 +020096 protected ExecutorService executor = Executors.newFixedThreadPool(
97 10, groupedThreads("onos/pipeconf-manager", "%d", log));
Andrea Campanellabc112a92017-06-26 19:06:43 +020098
Carmelo Cascone96beb6f2018-06-27 18:07:12 +020099 protected final ConfigFactory configFactory =
Andrea Campanellabc112a92017-06-26 19:06:43 +0200100 new ConfigFactory<DeviceId, PiPipeconfConfig>(
101 SubjectFactories.DEVICE_SUBJECT_FACTORY,
102 PiPipeconfConfig.class, CFG_SCHEME) {
103 @Override
104 public PiPipeconfConfig createConfig() {
105 return new PiPipeconfConfig();
106 }
107 };
108
Andrea Campanellabc112a92017-06-26 19:06:43 +0200109 @Activate
110 public void activate() {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200111 cfgService.registerConfigFactory(configFactory);
Carmelo Cascone0761cd32018-08-29 19:22:50 -0700112 driverAdminService.addListener(driverListener);
Carmelo Casconeda60a612018-08-24 00:01:34 -0700113 checkMissingMergedDrivers();
Andrea Campanellabc112a92017-06-26 19:06:43 +0200114 log.info("Started");
115 }
116
117
118 @Deactivate
119 public void deactivate() {
120 executor.shutdown();
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200121 cfgService.unregisterConfigFactory(configFactory);
Carmelo Cascone0761cd32018-08-29 19:22:50 -0700122 driverAdminService.removeListener(driverListener);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200123 pipeconfs.clear();
Carmelo Casconeda60a612018-08-24 00:01:34 -0700124 missingMergedDrivers.clear();
Andrea Campanellabc112a92017-06-26 19:06:43 +0200125 cfgService = null;
126 driverAdminService = null;
Andrea Campanellabc112a92017-06-26 19:06:43 +0200127 log.info("Stopped");
128 }
129
130 @Override
131 public void register(PiPipeconf pipeconf) throws IllegalStateException {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200132 if (pipeconfs.containsKey(pipeconf.id())) {
Carmelo Cascone44daf562017-07-16 23:55:08 -0400133 throw new IllegalStateException(format("Pipeconf %s is already registered", pipeconf.id()));
134 }
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200135 pipeconfs.put(pipeconf.id(), pipeconf);
Carmelo Cascone44daf562017-07-16 23:55:08 -0400136 log.info("New pipeconf registered: {}", pipeconf.id());
Carmelo Casconeda60a612018-08-24 00:01:34 -0700137 executor.execute(() -> mergeAll(pipeconf.id()));
Andrea Campanellabc112a92017-06-26 19:06:43 +0200138 }
139
140 @Override
Andrea Campanellaa9b3c9b2017-07-21 14:03:15 +0200141 public void remove(PiPipeconfId pipeconfId) throws IllegalStateException {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200142 // TODO add mechanism to remove from device.
143 if (!pipeconfs.containsKey(pipeconfId)) {
Andrea Campanellaa9b3c9b2017-07-21 14:03:15 +0200144 throw new IllegalStateException(format("Pipeconf %s is not registered", pipeconfId));
145 }
Andrea Campanellaf9c409a2017-07-13 14:14:41 +0200146 // TODO remove the binding from the distributed Store when the lifecycle of a pipeconf is defined.
147 // pipeconfMappingStore.removeBindings(pipeconfId);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200148 log.info("Removing pipeconf {}", pipeconfId);
149 pipeconfs.remove(pipeconfId);
Andrea Campanellaa9b3c9b2017-07-21 14:03:15 +0200150 }
151
152 @Override
Andrea Campanellabc112a92017-06-26 19:06:43 +0200153 public Iterable<PiPipeconf> getPipeconfs() {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200154 return pipeconfs.values();
Andrea Campanellabc112a92017-06-26 19:06:43 +0200155 }
156
157 @Override
158 public Optional<PiPipeconf> getPipeconf(PiPipeconfId id) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200159 return Optional.ofNullable(pipeconfs.get(id));
Andrea Campanellabc112a92017-06-26 19:06:43 +0200160 }
161
162 @Override
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200163 public void bindToDevice(PiPipeconfId pipeconfId, DeviceId deviceId) {
Carmelo Cascone0761cd32018-08-29 19:22:50 -0700164 PiPipeconfId existingPipeconfId = pipeconfMappingStore.getPipeconfId(deviceId);
165 if (existingPipeconfId != null && !existingPipeconfId.equals(pipeconfId)) {
166 log.error("Cannot set binding for {} to {} as one already exists ({})",
167 deviceId, pipeconfId, existingPipeconfId);
168 return;
169 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200170 pipeconfMappingStore.createOrUpdateBinding(deviceId, pipeconfId);
171 }
172
173 @Override
Carmelo Cascone0761cd32018-08-29 19:22:50 -0700174 public String getMergedDriver(DeviceId deviceId, PiPipeconfId pipeconfId) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200175 log.debug("Starting device driver merge of {} with {}...", deviceId, pipeconfId);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200176 final BasicDeviceConfig basicDeviceConfig = cfgService.getConfig(
177 deviceId, BasicDeviceConfig.class);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200178 if (basicDeviceConfig == null) {
179 log.warn("Unable to get basic device config for {}, " +
180 "aborting pipeconf driver merge");
181 return null;
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200182 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200183 String baseDriverName = basicDeviceConfig.driver();
Carmelo Casconeda60a612018-08-24 00:01:34 -0700184 if (isMergedDriverName(baseDriverName)) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200185 // The config already has driver name that is a merged one. We still
186 // need to make sure an instance of that merged driver is present in
187 // this node.
Carmelo Casconeda60a612018-08-24 00:01:34 -0700188 log.debug("Base driver of {} ({}) is a merged one",
189 deviceId, baseDriverName);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200190 baseDriverName = getBaseDriverNameFromMerged(baseDriverName);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200191 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200192
Carmelo Casconeda60a612018-08-24 00:01:34 -0700193 return doMergeDriver(baseDriverName, pipeconfId);
194 }
195
196 @Override
197 public Optional<PiPipeconfId> ofDevice(DeviceId deviceId) {
198 return Optional.ofNullable(pipeconfMappingStore.getPipeconfId(deviceId));
199 }
200
201 private String doMergeDriver(String baseDriverName, PiPipeconfId pipeconfId) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200202 final String newDriverName = mergedDriverName(baseDriverName, pipeconfId);
Carmelo Casconeda60a612018-08-24 00:01:34 -0700203 // Serialize per newDriverName, avoid creating duplicates.
204 locks.get(newDriverName).lock();
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200205 try {
Carmelo Casconeda60a612018-08-24 00:01:34 -0700206 // If merged driver exists already we don't create a new one.
207 if (getDriver(newDriverName) != null) {
208 return newDriverName;
209 }
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200210 log.info("Creating merged driver {}...", newDriverName);
Carmelo Casconeda60a612018-08-24 00:01:34 -0700211 final Driver mergedDriver = buildMergedDriver(
212 pipeconfId, baseDriverName, newDriverName);
213 if (mergedDriver == null) {
214 // Error logged by buildMergedDriver
215 return null;
216 }
217 registerMergedDriver(mergedDriver);
218 if (missingMergedDrivers.remove(newDriverName)) {
219 log.info("There are still {} missing merged drivers",
220 missingMergedDrivers.size());
221 }
222 return newDriverName;
223 } finally {
224 locks.get(newDriverName).unlock();
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200225 }
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200226 }
227
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200228 private String mergedDriverSuffix(PiPipeconfId pipeconfId) {
229 return MERGED_DRIVER_SEPARATOR + pipeconfId.id();
230 }
231
232 private String mergedDriverName(String baseDriverName, PiPipeconfId pipeconfId) {
233 return baseDriverName + mergedDriverSuffix(pipeconfId);
234 }
235
236 private String getBaseDriverNameFromMerged(String mergedDriverName) {
237 final String[] pieces = mergedDriverName.split(MERGED_DRIVER_SEPARATOR);
238 if (pieces.length != 2) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200239 return null;
240 }
241 return pieces[0];
242 }
243
Carmelo Casconeda60a612018-08-24 00:01:34 -0700244 private PiPipeconfId getPipeconfIdFromMerged(String mergedDriverName) {
245 final String[] pieces = mergedDriverName.split(MERGED_DRIVER_SEPARATOR);
246 if (pieces.length != 2) {
247 return null;
248 }
249 return new PiPipeconfId(pieces[1]);
250 }
251
252 private boolean isMergedDriverName(String driverName) {
253 final String[] pieces = driverName.split(MERGED_DRIVER_SEPARATOR);
254 return pieces.length == 2;
255 }
256
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200257 private Driver buildMergedDriver(PiPipeconfId pipeconfId, String baseDriverName,
258 String newDriverName) {
Carmelo Casconeda60a612018-08-24 00:01:34 -0700259 final Driver baseDriver = getDriver(baseDriverName);
260 if (baseDriver == null) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200261 log.error("Base driver {} not found, cannot build a merged one",
262 baseDriverName);
263 return null;
264 }
265
266 final PiPipeconf pipeconf = pipeconfs.get(pipeconfId);
267 if (pipeconf == null) {
268 log.error("Pipeconf {} is not registered, cannot build a merged driver",
269 pipeconfId);
270 return null;
271 }
272
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200273 // extract the behaviours from the pipipeconf.
274 final Map<Class<? extends Behaviour>, Class<? extends Behaviour>> behaviours =
275 new HashMap<>();
276 pipeconf.behaviours().forEach(
277 b -> behaviours.put(b, pipeconf.implementation(b).get()));
278 final Driver piPipeconfDriver = new DefaultDriver(
279 newDriverName, baseDriver.parents(),
280 baseDriver.manufacturer(), baseDriver.hwVersion(),
281 baseDriver.swVersion(), behaviours, new HashMap<>());
282 // take the base driver created with the behaviours of the PiPeconf and
283 // merge it with the base driver that was assigned to the device
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200284 return piPipeconfDriver.merge(baseDriver);
285 }
286
287 private void registerMergedDriver(Driver driver) {
288 final DriverProvider provider = new InternalDriverProvider(driver);
289 if (driverAdminService.getProviders().contains(provider)) {
290 // A provider for this driver already exist.
291 return;
292 }
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200293 driverAdminService.registerProvider(provider);
294 }
295
Carmelo Casconeda60a612018-08-24 00:01:34 -0700296 private Driver getDriver(String name) {
297 try {
Carmelo Cascone0761cd32018-08-29 19:22:50 -0700298 return driverAdminService.getDriver(name);
Carmelo Casconeda60a612018-08-24 00:01:34 -0700299 } catch (ItemNotFoundException e) {
300 return null;
301 }
302 }
303
304 private void checkMissingMergedDrivers() {
305 cfgService.getSubjects(DeviceId.class, BasicDeviceConfig.class).stream()
306 .map(d -> cfgService.getConfig(d, BasicDeviceConfig.class))
307 .map(BasicDeviceConfig::driver)
308 .filter(Objects::nonNull)
309 .filter(d -> getDriver(d) == null)
310 .forEach(driverName -> {
311 final String baseDriverName = getBaseDriverNameFromMerged(driverName);
312 final PiPipeconfId pipeconfId = getPipeconfIdFromMerged(driverName);
313 if (baseDriverName == null || pipeconfId == null) {
314 // Not a merged driver.
315 return;
316 }
317 log.info("Detected missing merged driver: {}", driverName);
318 missingMergedDrivers.add(driverName);
319 // Attempt building the driver now if all pieces are present.
320 // If not, either a driver or pipeconf event will re-trigger
321 // the merge process.
322 if (getDriver(baseDriverName) != null
323 && pipeconfs.containsKey(pipeconfId)) {
324 mergedDriverName(baseDriverName, pipeconfId);
325 }
326 });
327 }
328
329 private void mergeAll(String baseDriverName) {
330 missingMergedDrivers.stream()
331 .filter(driverName -> {
332 final String xx = getBaseDriverNameFromMerged(driverName);
333 return xx != null && xx.equals(baseDriverName);
334 })
335 .forEach(driverName -> {
336 final PiPipeconfId pipeconfId = getPipeconfIdFromMerged(driverName);
337 if (pipeconfs.containsKey(pipeconfId)) {
338 doMergeDriver(baseDriverName, pipeconfId);
339 }
340 });
341 }
342
343 private void mergeAll(PiPipeconfId pipeconfId) {
344 missingMergedDrivers.stream()
345 .filter(driverName -> {
346 final PiPipeconfId xx = getPipeconfIdFromMerged(driverName);
347 return xx != null && xx.equals(pipeconfId);
348 })
349 .forEach(driverName -> {
350 final String baseDriverName = getBaseDriverNameFromMerged(driverName);
351 if (getDriver(baseDriverName) != null) {
352 doMergeDriver(baseDriverName, pipeconfId);
353 }
354 });
355 }
356
357 private class InternalDriverListener implements DriverListener {
358
359 @Override
360 public void event(DriverEvent event) {
361 executor.execute(() -> mergeAll(event.subject().name()));
362 }
363
364 @Override
365 public boolean isRelevant(DriverEvent event) {
366 return event.type() == DriverEvent.Type.DRIVER_ENHANCED;
367 }
368 }
369
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200370 /**
371 * Internal driver provider used to register merged pipeconf drivers in the
372 * core.
373 */
374 private class InternalDriverProvider implements DriverProvider {
Andrea Campanellabc112a92017-06-26 19:06:43 +0200375
376 Driver driver;
377
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200378 InternalDriverProvider(Driver driver) {
Andrea Campanellabc112a92017-06-26 19:06:43 +0200379 this.driver = driver;
380 }
381
382 @Override
383 public Set<Driver> getDrivers() {
384 return ImmutableSet.of(driver);
385 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200386
387 @Override
388 public boolean equals(Object o) {
389 if (this == o) {
390 return true;
391 }
392 if (o == null || getClass() != o.getClass()) {
393 return false;
394 }
395 InternalDriverProvider that = (InternalDriverProvider) o;
Carmelo Casconeda60a612018-08-24 00:01:34 -0700396 return Objects.equals(driver.name(), that.driver.name());
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200397 }
398
399 @Override
400 public int hashCode() {
401 return Objects.hashCode(driver.name());
402 }
Andrea Campanellabc112a92017-06-26 19:06:43 +0200403 }
Andrea Campanellabc112a92017-06-26 19:06:43 +0200404}