blob: 1846ab4bdbaf92c089cb86e371ca2b30f299af24 [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.onlab.util.ItemNotFoundException;
24import org.onosproject.net.DeviceId;
25import org.onosproject.net.config.ConfigFactory;
Andrea Campanellabc112a92017-06-26 19:06:43 +020026import org.onosproject.net.config.NetworkConfigRegistry;
27import org.onosproject.net.config.basics.BasicDeviceConfig;
28import org.onosproject.net.config.basics.SubjectFactories;
29import org.onosproject.net.driver.Behaviour;
30import org.onosproject.net.driver.DefaultDriver;
31import org.onosproject.net.driver.Driver;
32import org.onosproject.net.driver.DriverAdminService;
Carmelo Casconeda60a612018-08-24 00:01:34 -070033import org.onosproject.net.driver.DriverEvent;
34import org.onosproject.net.driver.DriverListener;
Andrea Campanellabc112a92017-06-26 19:06:43 +020035import org.onosproject.net.driver.DriverProvider;
Andrea Campanellabc112a92017-06-26 19:06:43 +020036import org.onosproject.net.pi.model.PiPipeconf;
37import org.onosproject.net.pi.model.PiPipeconfId;
Carmelo Cascone39c28ca2017-11-15 13:03:57 -080038import org.onosproject.net.pi.service.PiPipeconfConfig;
39import org.onosproject.net.pi.service.PiPipeconfMappingStore;
40import org.onosproject.net.pi.service.PiPipeconfService;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070041import org.osgi.service.component.annotations.Activate;
42import org.osgi.service.component.annotations.Component;
43import org.osgi.service.component.annotations.Deactivate;
44import org.osgi.service.component.annotations.Reference;
45import org.osgi.service.component.annotations.ReferenceCardinality;
Andrea Campanellabc112a92017-06-26 19:06:43 +020046import org.slf4j.Logger;
47
48import java.util.HashMap;
49import java.util.Map;
Carmelo Casconeda60a612018-08-24 00:01:34 -070050import java.util.Objects;
Andrea Campanellabc112a92017-06-26 19:06:43 +020051import java.util.Optional;
52import java.util.Set;
Andrea Campanellabc112a92017-06-26 19:06:43 +020053import java.util.concurrent.ConcurrentHashMap;
Carmelo Cascone96beb6f2018-06-27 18:07:12 +020054import java.util.concurrent.ConcurrentMap;
Andrea Campanellabc112a92017-06-26 19:06:43 +020055import java.util.concurrent.ExecutorService;
56import java.util.concurrent.Executors;
Carmelo Casconeda60a612018-08-24 00:01:34 -070057import java.util.concurrent.locks.Lock;
Andrea Campanellabc112a92017-06-26 19:06:43 +020058
Carmelo Cascone44daf562017-07-16 23:55:08 -040059import static java.lang.String.format;
Andrea Campanellabc112a92017-06-26 19:06:43 +020060import static org.onlab.util.Tools.groupedThreads;
61import static org.slf4j.LoggerFactory.getLogger;
62
63
64/**
65 * Implementation of the PiPipeconfService.
66 */
Ray Milkeyd84f89b2018-08-17 14:54:17 -070067@Component(immediate = true, service = PiPipeconfService.class)
Andrea Campanella48f99fa2017-07-13 19:06:21 +020068@Beta
69public class PiPipeconfManager implements PiPipeconfService {
Andrea Campanellabc112a92017-06-26 19:06:43 +020070
71 private final Logger log = getLogger(getClass());
72
Carmelo Cascone158b8c42018-07-04 19:42:37 +020073 private static final String MERGED_DRIVER_SEPARATOR = ":";
Andrea Campanellabc112a92017-06-26 19:06:43 +020074 private static final String CFG_SCHEME = "piPipeconf";
75
Ray Milkeyd84f89b2018-08-17 14:54:17 -070076 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Andrea Campanellabc112a92017-06-26 19:06:43 +020077 protected NetworkConfigRegistry cfgService;
78
Ray Milkeyd84f89b2018-08-17 14:54:17 -070079 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Andrea Campanellabc112a92017-06-26 19:06:43 +020080 protected DriverAdminService driverAdminService;
81
Ray Milkeyd84f89b2018-08-17 14:54:17 -070082 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Cascone96beb6f2018-06-27 18:07:12 +020083 private PiPipeconfMappingStore pipeconfMappingStore;
Andrea Campanellaf9c409a2017-07-13 14:14:41 +020084
Carmelo Cascone96beb6f2018-06-27 18:07:12 +020085 // Registered pipeconf are replicated through the app subsystem and
86 // registered on app activated events. Hence, there should be no need of
87 // distributing this map.
88 protected ConcurrentMap<PiPipeconfId, PiPipeconf> pipeconfs = new ConcurrentHashMap<>();
Andrea Campanellabc112a92017-06-26 19:06:43 +020089
Carmelo Casconeda60a612018-08-24 00:01:34 -070090 private final DriverListener driverListener = new InternalDriverListener();
91 private final Set<String> missingMergedDrivers = Sets.newCopyOnWriteArraySet();
92 private final Striped<Lock> locks = Striped.lock(20);
93
Carmelo Cascone96beb6f2018-06-27 18:07:12 +020094 protected ExecutorService executor = Executors.newFixedThreadPool(
95 10, groupedThreads("onos/pipeconf-manager", "%d", log));
Andrea Campanellabc112a92017-06-26 19:06:43 +020096
Carmelo Cascone96beb6f2018-06-27 18:07:12 +020097 protected final ConfigFactory configFactory =
Andrea Campanellabc112a92017-06-26 19:06:43 +020098 new ConfigFactory<DeviceId, PiPipeconfConfig>(
99 SubjectFactories.DEVICE_SUBJECT_FACTORY,
100 PiPipeconfConfig.class, CFG_SCHEME) {
101 @Override
102 public PiPipeconfConfig createConfig() {
103 return new PiPipeconfConfig();
104 }
105 };
106
Andrea Campanellabc112a92017-06-26 19:06:43 +0200107 @Activate
108 public void activate() {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200109 cfgService.registerConfigFactory(configFactory);
Carmelo Cascone0761cd32018-08-29 19:22:50 -0700110 driverAdminService.addListener(driverListener);
Carmelo Casconeda60a612018-08-24 00:01:34 -0700111 checkMissingMergedDrivers();
Andrea Campanellabc112a92017-06-26 19:06:43 +0200112 log.info("Started");
113 }
114
115
116 @Deactivate
117 public void deactivate() {
118 executor.shutdown();
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200119 cfgService.unregisterConfigFactory(configFactory);
Carmelo Cascone0761cd32018-08-29 19:22:50 -0700120 driverAdminService.removeListener(driverListener);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200121 pipeconfs.clear();
Carmelo Casconeda60a612018-08-24 00:01:34 -0700122 missingMergedDrivers.clear();
Andrea Campanellabc112a92017-06-26 19:06:43 +0200123 cfgService = null;
124 driverAdminService = null;
Andrea Campanellabc112a92017-06-26 19:06:43 +0200125 log.info("Stopped");
126 }
127
128 @Override
129 public void register(PiPipeconf pipeconf) throws IllegalStateException {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200130 if (pipeconfs.containsKey(pipeconf.id())) {
Carmelo Cascone44daf562017-07-16 23:55:08 -0400131 throw new IllegalStateException(format("Pipeconf %s is already registered", pipeconf.id()));
132 }
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200133 pipeconfs.put(pipeconf.id(), pipeconf);
Carmelo Cascone44daf562017-07-16 23:55:08 -0400134 log.info("New pipeconf registered: {}", pipeconf.id());
Carmelo Casconeda60a612018-08-24 00:01:34 -0700135 executor.execute(() -> mergeAll(pipeconf.id()));
Andrea Campanellabc112a92017-06-26 19:06:43 +0200136 }
137
138 @Override
Andrea Campanellaa9b3c9b2017-07-21 14:03:15 +0200139 public void remove(PiPipeconfId pipeconfId) throws IllegalStateException {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200140 // TODO add mechanism to remove from device.
141 if (!pipeconfs.containsKey(pipeconfId)) {
Andrea Campanellaa9b3c9b2017-07-21 14:03:15 +0200142 throw new IllegalStateException(format("Pipeconf %s is not registered", pipeconfId));
143 }
Andrea Campanellaf9c409a2017-07-13 14:14:41 +0200144 // TODO remove the binding from the distributed Store when the lifecycle of a pipeconf is defined.
145 // pipeconfMappingStore.removeBindings(pipeconfId);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200146 log.info("Removing pipeconf {}", pipeconfId);
147 pipeconfs.remove(pipeconfId);
Andrea Campanellaa9b3c9b2017-07-21 14:03:15 +0200148 }
149
150 @Override
Andrea Campanellabc112a92017-06-26 19:06:43 +0200151 public Iterable<PiPipeconf> getPipeconfs() {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200152 return pipeconfs.values();
Andrea Campanellabc112a92017-06-26 19:06:43 +0200153 }
154
155 @Override
156 public Optional<PiPipeconf> getPipeconf(PiPipeconfId id) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200157 return Optional.ofNullable(pipeconfs.get(id));
Andrea Campanellabc112a92017-06-26 19:06:43 +0200158 }
159
160 @Override
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200161 public void bindToDevice(PiPipeconfId pipeconfId, DeviceId deviceId) {
Carmelo Cascone0761cd32018-08-29 19:22:50 -0700162 PiPipeconfId existingPipeconfId = pipeconfMappingStore.getPipeconfId(deviceId);
163 if (existingPipeconfId != null && !existingPipeconfId.equals(pipeconfId)) {
164 log.error("Cannot set binding for {} to {} as one already exists ({})",
165 deviceId, pipeconfId, existingPipeconfId);
166 return;
167 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200168 pipeconfMappingStore.createOrUpdateBinding(deviceId, pipeconfId);
169 }
170
171 @Override
Carmelo Cascone0761cd32018-08-29 19:22:50 -0700172 public String getMergedDriver(DeviceId deviceId, PiPipeconfId pipeconfId) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200173 log.debug("Starting device driver merge of {} with {}...", deviceId, pipeconfId);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200174 final BasicDeviceConfig basicDeviceConfig = cfgService.getConfig(
175 deviceId, BasicDeviceConfig.class);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200176 if (basicDeviceConfig == null) {
177 log.warn("Unable to get basic device config for {}, " +
178 "aborting pipeconf driver merge");
179 return null;
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200180 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200181 String baseDriverName = basicDeviceConfig.driver();
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700182 if (baseDriverName == null) {
183 log.warn("Missing driver from basic device config for {}, " +
184 "cannot produce merged driver", deviceId);
185 return null;
186 }
Carmelo Casconeda60a612018-08-24 00:01:34 -0700187 if (isMergedDriverName(baseDriverName)) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200188 // The config already has driver name that is a merged one. We still
189 // need to make sure an instance of that merged driver is present in
190 // this node.
Carmelo Casconeda60a612018-08-24 00:01:34 -0700191 log.debug("Base driver of {} ({}) is a merged one",
192 deviceId, baseDriverName);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200193 baseDriverName = getBaseDriverNameFromMerged(baseDriverName);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200194 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200195
Carmelo Casconeda60a612018-08-24 00:01:34 -0700196 return doMergeDriver(baseDriverName, pipeconfId);
197 }
198
199 @Override
200 public Optional<PiPipeconfId> ofDevice(DeviceId deviceId) {
201 return Optional.ofNullable(pipeconfMappingStore.getPipeconfId(deviceId));
202 }
203
204 private String doMergeDriver(String baseDriverName, PiPipeconfId pipeconfId) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200205 final String newDriverName = mergedDriverName(baseDriverName, pipeconfId);
Carmelo Casconeda60a612018-08-24 00:01:34 -0700206 // Serialize per newDriverName, avoid creating duplicates.
207 locks.get(newDriverName).lock();
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200208 try {
Carmelo Casconeda60a612018-08-24 00:01:34 -0700209 // If merged driver exists already we don't create a new one.
210 if (getDriver(newDriverName) != null) {
211 return newDriverName;
212 }
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200213 log.info("Creating merged driver {}...", newDriverName);
Carmelo Casconeda60a612018-08-24 00:01:34 -0700214 final Driver mergedDriver = buildMergedDriver(
215 pipeconfId, baseDriverName, newDriverName);
216 if (mergedDriver == null) {
217 // Error logged by buildMergedDriver
218 return null;
219 }
220 registerMergedDriver(mergedDriver);
221 if (missingMergedDrivers.remove(newDriverName)) {
222 log.info("There are still {} missing merged drivers",
223 missingMergedDrivers.size());
224 }
225 return newDriverName;
226 } finally {
227 locks.get(newDriverName).unlock();
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200228 }
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200229 }
230
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200231 private String mergedDriverSuffix(PiPipeconfId pipeconfId) {
232 return MERGED_DRIVER_SEPARATOR + pipeconfId.id();
233 }
234
235 private String mergedDriverName(String baseDriverName, PiPipeconfId pipeconfId) {
236 return baseDriverName + mergedDriverSuffix(pipeconfId);
237 }
238
239 private String getBaseDriverNameFromMerged(String mergedDriverName) {
240 final String[] pieces = mergedDriverName.split(MERGED_DRIVER_SEPARATOR);
241 if (pieces.length != 2) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200242 return null;
243 }
244 return pieces[0];
245 }
246
Carmelo Casconeda60a612018-08-24 00:01:34 -0700247 private PiPipeconfId getPipeconfIdFromMerged(String mergedDriverName) {
248 final String[] pieces = mergedDriverName.split(MERGED_DRIVER_SEPARATOR);
249 if (pieces.length != 2) {
250 return null;
251 }
252 return new PiPipeconfId(pieces[1]);
253 }
254
255 private boolean isMergedDriverName(String driverName) {
256 final String[] pieces = driverName.split(MERGED_DRIVER_SEPARATOR);
257 return pieces.length == 2;
258 }
259
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200260 private Driver buildMergedDriver(PiPipeconfId pipeconfId, String baseDriverName,
261 String newDriverName) {
Carmelo Casconeda60a612018-08-24 00:01:34 -0700262 final Driver baseDriver = getDriver(baseDriverName);
263 if (baseDriver == null) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200264 log.error("Base driver {} not found, cannot build a merged one",
265 baseDriverName);
266 return null;
267 }
268
269 final PiPipeconf pipeconf = pipeconfs.get(pipeconfId);
270 if (pipeconf == null) {
271 log.error("Pipeconf {} is not registered, cannot build a merged driver",
272 pipeconfId);
273 return null;
274 }
275
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200276 // extract the behaviours from the pipipeconf.
277 final Map<Class<? extends Behaviour>, Class<? extends Behaviour>> behaviours =
278 new HashMap<>();
279 pipeconf.behaviours().forEach(
280 b -> behaviours.put(b, pipeconf.implementation(b).get()));
281 final Driver piPipeconfDriver = new DefaultDriver(
282 newDriverName, baseDriver.parents(),
283 baseDriver.manufacturer(), baseDriver.hwVersion(),
284 baseDriver.swVersion(), behaviours, new HashMap<>());
285 // take the base driver created with the behaviours of the PiPeconf and
286 // merge it with the base driver that was assigned to the device
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200287 return piPipeconfDriver.merge(baseDriver);
288 }
289
290 private void registerMergedDriver(Driver driver) {
291 final DriverProvider provider = new InternalDriverProvider(driver);
292 if (driverAdminService.getProviders().contains(provider)) {
293 // A provider for this driver already exist.
294 return;
295 }
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200296 driverAdminService.registerProvider(provider);
297 }
298
Carmelo Casconeda60a612018-08-24 00:01:34 -0700299 private Driver getDriver(String name) {
300 try {
Carmelo Cascone0761cd32018-08-29 19:22:50 -0700301 return driverAdminService.getDriver(name);
Carmelo Casconeda60a612018-08-24 00:01:34 -0700302 } catch (ItemNotFoundException e) {
303 return null;
304 }
305 }
306
307 private void checkMissingMergedDrivers() {
308 cfgService.getSubjects(DeviceId.class, BasicDeviceConfig.class).stream()
309 .map(d -> cfgService.getConfig(d, BasicDeviceConfig.class))
310 .map(BasicDeviceConfig::driver)
311 .filter(Objects::nonNull)
312 .filter(d -> getDriver(d) == null)
313 .forEach(driverName -> {
314 final String baseDriverName = getBaseDriverNameFromMerged(driverName);
315 final PiPipeconfId pipeconfId = getPipeconfIdFromMerged(driverName);
316 if (baseDriverName == null || pipeconfId == null) {
317 // Not a merged driver.
318 return;
319 }
320 log.info("Detected missing merged driver: {}", driverName);
321 missingMergedDrivers.add(driverName);
322 // Attempt building the driver now if all pieces are present.
323 // If not, either a driver or pipeconf event will re-trigger
324 // the merge process.
325 if (getDriver(baseDriverName) != null
326 && pipeconfs.containsKey(pipeconfId)) {
327 mergedDriverName(baseDriverName, pipeconfId);
328 }
329 });
330 }
331
332 private void mergeAll(String baseDriverName) {
333 missingMergedDrivers.stream()
334 .filter(driverName -> {
335 final String xx = getBaseDriverNameFromMerged(driverName);
336 return xx != null && xx.equals(baseDriverName);
337 })
338 .forEach(driverName -> {
339 final PiPipeconfId pipeconfId = getPipeconfIdFromMerged(driverName);
340 if (pipeconfs.containsKey(pipeconfId)) {
341 doMergeDriver(baseDriverName, pipeconfId);
342 }
343 });
344 }
345
346 private void mergeAll(PiPipeconfId pipeconfId) {
347 missingMergedDrivers.stream()
348 .filter(driverName -> {
349 final PiPipeconfId xx = getPipeconfIdFromMerged(driverName);
350 return xx != null && xx.equals(pipeconfId);
351 })
352 .forEach(driverName -> {
353 final String baseDriverName = getBaseDriverNameFromMerged(driverName);
354 if (getDriver(baseDriverName) != null) {
355 doMergeDriver(baseDriverName, pipeconfId);
356 }
357 });
358 }
359
360 private class InternalDriverListener implements DriverListener {
361
362 @Override
363 public void event(DriverEvent event) {
364 executor.execute(() -> mergeAll(event.subject().name()));
365 }
366
367 @Override
368 public boolean isRelevant(DriverEvent event) {
369 return event.type() == DriverEvent.Type.DRIVER_ENHANCED;
370 }
371 }
372
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200373 /**
374 * Internal driver provider used to register merged pipeconf drivers in the
375 * core.
376 */
377 private class InternalDriverProvider implements DriverProvider {
Andrea Campanellabc112a92017-06-26 19:06:43 +0200378
379 Driver driver;
380
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200381 InternalDriverProvider(Driver driver) {
Andrea Campanellabc112a92017-06-26 19:06:43 +0200382 this.driver = driver;
383 }
384
385 @Override
386 public Set<Driver> getDrivers() {
387 return ImmutableSet.of(driver);
388 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200389
390 @Override
391 public boolean equals(Object o) {
392 if (this == o) {
393 return true;
394 }
395 if (o == null || getClass() != o.getClass()) {
396 return false;
397 }
398 InternalDriverProvider that = (InternalDriverProvider) o;
Carmelo Casconeda60a612018-08-24 00:01:34 -0700399 return Objects.equals(driver.name(), that.driver.name());
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200400 }
401
402 @Override
403 public int hashCode() {
404 return Objects.hashCode(driver.name());
405 }
Andrea Campanellabc112a92017-06-26 19:06:43 +0200406 }
Andrea Campanellabc112a92017-06-26 19:06:43 +0200407}