blob: ba16601c1768261ce84cf0aa5bd11338fed4d78b [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
19import com.fasterxml.jackson.databind.JsonNode;
20import com.fasterxml.jackson.databind.ObjectMapper;
21import com.fasterxml.jackson.databind.node.ObjectNode;
Andrea Campanella48f99fa2017-07-13 19:06:21 +020022import com.google.common.annotations.Beta;
Andrea Campanellabc112a92017-06-26 19:06:43 +020023import com.google.common.collect.ImmutableSet;
24import org.apache.felix.scr.annotations.Activate;
25import org.apache.felix.scr.annotations.Component;
26import org.apache.felix.scr.annotations.Deactivate;
27import org.apache.felix.scr.annotations.Reference;
28import org.apache.felix.scr.annotations.ReferenceCardinality;
29import org.apache.felix.scr.annotations.Service;
30import org.onlab.util.ItemNotFoundException;
Andrea Campanella14e196d2017-07-24 18:11:36 +020031import org.onosproject.cluster.ClusterService;
32import org.onosproject.cluster.LeadershipService;
Andrea Campanellabc112a92017-06-26 19:06:43 +020033import org.onosproject.net.DeviceId;
34import org.onosproject.net.config.ConfigFactory;
Andrea Campanellabc112a92017-06-26 19:06:43 +020035import org.onosproject.net.config.NetworkConfigRegistry;
36import org.onosproject.net.config.basics.BasicDeviceConfig;
37import org.onosproject.net.config.basics.SubjectFactories;
38import org.onosproject.net.driver.Behaviour;
39import org.onosproject.net.driver.DefaultDriver;
40import org.onosproject.net.driver.Driver;
41import org.onosproject.net.driver.DriverAdminService;
42import org.onosproject.net.driver.DriverProvider;
43import org.onosproject.net.driver.DriverService;
44import org.onosproject.net.pi.model.PiPipeconf;
45import org.onosproject.net.pi.model.PiPipeconfId;
Carmelo Cascone39c28ca2017-11-15 13:03:57 -080046import org.onosproject.net.pi.service.PiPipeconfConfig;
47import org.onosproject.net.pi.service.PiPipeconfMappingStore;
48import org.onosproject.net.pi.service.PiPipeconfService;
Andrea Campanellabc112a92017-06-26 19:06:43 +020049import org.slf4j.Logger;
50
51import java.util.HashMap;
52import java.util.Map;
53import java.util.Optional;
54import java.util.Set;
55import java.util.concurrent.CompletableFuture;
56import java.util.concurrent.ConcurrentHashMap;
Carmelo Cascone96beb6f2018-06-27 18:07:12 +020057import java.util.concurrent.ConcurrentMap;
Andrea Campanellabc112a92017-06-26 19:06:43 +020058import java.util.concurrent.ExecutorService;
59import java.util.concurrent.Executors;
60
Carmelo Cascone44daf562017-07-16 23:55:08 -040061import static java.lang.String.format;
Andrea Campanellabc112a92017-06-26 19:06:43 +020062import static org.onlab.util.Tools.groupedThreads;
63import static org.slf4j.LoggerFactory.getLogger;
64
65
66/**
67 * Implementation of the PiPipeconfService.
68 */
69@Component(immediate = true)
70@Service
Andrea Campanella48f99fa2017-07-13 19:06:21 +020071@Beta
72public class PiPipeconfManager implements PiPipeconfService {
Andrea Campanellabc112a92017-06-26 19:06:43 +020073
74 private final Logger log = getLogger(getClass());
75
76 private static final String DRIVER = "driver";
77 private static final String CFG_SCHEME = "piPipeconf";
78
Carmelo Cascone96beb6f2018-06-27 18:07:12 +020079 private static final String DRIVER_MERGE_TOPIC =
80 PiPipeconfManager.class.getSimpleName() + "-driver-merge-";
81
Andrea Campanellabc112a92017-06-26 19:06:43 +020082 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
83 protected NetworkConfigRegistry cfgService;
84
85 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Carmelo Cascone96beb6f2018-06-27 18:07:12 +020086 private LeadershipService leadershipService;
Andrea Campanella14e196d2017-07-24 18:11:36 +020087
88 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Andrea Campanellabc112a92017-06-26 19:06:43 +020089 protected DriverService driverService;
90
91 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
92 protected DriverAdminService driverAdminService;
93
Andrea Campanellaf9c409a2017-07-13 14:14:41 +020094 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Carmelo Cascone96beb6f2018-06-27 18:07:12 +020095 private PiPipeconfMappingStore pipeconfMappingStore;
Andrea Campanellaf9c409a2017-07-13 14:14:41 +020096
Andrea Campanella14e196d2017-07-24 18:11:36 +020097 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
98 protected ClusterService clusterService;
99
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200100 // Registered pipeconf are replicated through the app subsystem and
101 // registered on app activated events. Hence, there should be no need of
102 // distributing this map.
103 protected ConcurrentMap<PiPipeconfId, PiPipeconf> pipeconfs = new ConcurrentHashMap<>();
Andrea Campanellabc112a92017-06-26 19:06:43 +0200104
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200105 protected ExecutorService executor = Executors.newFixedThreadPool(
106 10, groupedThreads("onos/pipeconf-manager", "%d", log));
Andrea Campanellabc112a92017-06-26 19:06:43 +0200107
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200108 protected final ConfigFactory configFactory =
Andrea Campanellabc112a92017-06-26 19:06:43 +0200109 new ConfigFactory<DeviceId, PiPipeconfConfig>(
110 SubjectFactories.DEVICE_SUBJECT_FACTORY,
111 PiPipeconfConfig.class, CFG_SCHEME) {
112 @Override
113 public PiPipeconfConfig createConfig() {
114 return new PiPipeconfConfig();
115 }
116 };
117
Andrea Campanellabc112a92017-06-26 19:06:43 +0200118 @Activate
119 public void activate() {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200120 cfgService.registerConfigFactory(configFactory);
Andrea Campanellabc112a92017-06-26 19:06:43 +0200121 log.info("Started");
122 }
123
124
125 @Deactivate
126 public void deactivate() {
127 executor.shutdown();
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200128 cfgService.unregisterConfigFactory(configFactory);
129 pipeconfs.clear();
Andrea Campanellabc112a92017-06-26 19:06:43 +0200130 cfgService = null;
131 driverAdminService = null;
132 driverService = null;
133 log.info("Stopped");
134 }
135
136 @Override
137 public void register(PiPipeconf pipeconf) throws IllegalStateException {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200138 if (pipeconfs.containsKey(pipeconf.id())) {
Carmelo Cascone44daf562017-07-16 23:55:08 -0400139 throw new IllegalStateException(format("Pipeconf %s is already registered", pipeconf.id()));
140 }
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200141 pipeconfs.put(pipeconf.id(), pipeconf);
Carmelo Cascone44daf562017-07-16 23:55:08 -0400142 log.info("New pipeconf registered: {}", pipeconf.id());
Andrea Campanellabc112a92017-06-26 19:06:43 +0200143 }
144
145 @Override
Andrea Campanellaa9b3c9b2017-07-21 14:03:15 +0200146 public void remove(PiPipeconfId pipeconfId) throws IllegalStateException {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200147 // TODO add mechanism to remove from device.
148 if (!pipeconfs.containsKey(pipeconfId)) {
Andrea Campanellaa9b3c9b2017-07-21 14:03:15 +0200149 throw new IllegalStateException(format("Pipeconf %s is not registered", pipeconfId));
150 }
Andrea Campanellaf9c409a2017-07-13 14:14:41 +0200151 // TODO remove the binding from the distributed Store when the lifecycle of a pipeconf is defined.
152 // pipeconfMappingStore.removeBindings(pipeconfId);
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200153 log.info("Removing pipeconf {}", pipeconfId);
154 pipeconfs.remove(pipeconfId);
Andrea Campanellaa9b3c9b2017-07-21 14:03:15 +0200155 }
156
157 @Override
Andrea Campanellabc112a92017-06-26 19:06:43 +0200158 public Iterable<PiPipeconf> getPipeconfs() {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200159 return pipeconfs.values();
Andrea Campanellabc112a92017-06-26 19:06:43 +0200160 }
161
162 @Override
163 public Optional<PiPipeconf> getPipeconf(PiPipeconfId id) {
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200164 return Optional.ofNullable(pipeconfs.get(id));
Andrea Campanellabc112a92017-06-26 19:06:43 +0200165 }
166
167 @Override
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200168 public CompletableFuture<Boolean> bindToDevice(
169 PiPipeconfId pipeconfId, DeviceId deviceId) {
170 return CompletableFuture.supplyAsync(() -> doMergeDriver(
171 deviceId, pipeconfId), executor);
Andrea Campanellabc112a92017-06-26 19:06:43 +0200172 }
173
174 @Override
175 public Optional<PiPipeconfId> ofDevice(DeviceId deviceId) {
Andrea Campanellaf9c409a2017-07-13 14:14:41 +0200176 return Optional.ofNullable(pipeconfMappingStore.getPipeconfId(deviceId));
Andrea Campanellabc112a92017-06-26 19:06:43 +0200177 }
178
Carmelo Cascone96beb6f2018-06-27 18:07:12 +0200179 private boolean doMergeDriver(DeviceId deviceId, PiPipeconfId pipeconfId) {
180 // Perform the following operations:
181 // 1) ALL nodes: create and register new merged driver (pipeconf + base driver)
182 // 2) ONE node (leader): updated netcfg with new driver
183 log.warn("Starting device driver merge of {} with {}...", deviceId, pipeconfId);
184 final BasicDeviceConfig basicDeviceConfig = cfgService.getConfig(
185 deviceId, BasicDeviceConfig.class);
186 final Driver baseDriver = driverService.getDriver(
187 basicDeviceConfig.driver());
188 final String newDriverName = baseDriver.name() + ":" + pipeconfId;
189 if (baseDriver.name().equals(newDriverName)) {
190 log.warn("Requested to merge {} driver with {} for {}, "
191 + "but current device driver is already merged",
192 baseDriver.name(), pipeconfId, deviceId);
193 return true;
194 }
195 final PiPipeconf pipeconf = pipeconfs.get(pipeconfId);
196 if (pipeconf == null) {
197 log.error("Pipeconf {} is not registered", pipeconfId);
198 return false;
199 }
200 // 1) if merged driver exists already we don't create a new one.
201 try {
202 driverService.getDriver(newDriverName);
203 log.info("Found existing merged driver {}, re-using that", newDriverName);
204 } catch (ItemNotFoundException e) {
205 log.info("Creating merged driver {}...", newDriverName);
206 createMergedDriver(pipeconf, baseDriver, newDriverName);
207 }
208 // 2) Updating device cfg to enforce the merged driver (one node only)
209 final boolean isLeader = leadershipService
210 .runForLeadership(DRIVER_MERGE_TOPIC + deviceId.toString())
211 .leaderNodeId()
212 .equals(clusterService.getLocalNode().id());
213 if (isLeader) {
214 // FIXME: this binding should be updated by the same entity
215 // deploying the pipeconf.
216 pipeconfMappingStore.createOrUpdateBinding(deviceId, pipeconfId);
217 if (!basicDeviceConfig.driver().equals(newDriverName)) {
218 log.info("Applying new driver {} for device {} via cfg...",
219 newDriverName, deviceId);
220 setDriverViaCfg(deviceId, newDriverName, basicDeviceConfig);
221 }
222 }
223 return true;
224 }
225
226 private void createMergedDriver(PiPipeconf pipeconf, Driver baseDriver,
227 String newDriverName) {
228 // extract the behaviours from the pipipeconf.
229 final Map<Class<? extends Behaviour>, Class<? extends Behaviour>> behaviours =
230 new HashMap<>();
231 pipeconf.behaviours().forEach(
232 b -> behaviours.put(b, pipeconf.implementation(b).get()));
233 final Driver piPipeconfDriver = new DefaultDriver(
234 newDriverName, baseDriver.parents(),
235 baseDriver.manufacturer(), baseDriver.hwVersion(),
236 baseDriver.swVersion(), behaviours, new HashMap<>());
237 // take the base driver created with the behaviours of the PiPeconf and
238 // merge it with the base driver that was assigned to the device
239 final Driver completeDriver = piPipeconfDriver.merge(baseDriver);
240 // This might lead to explosion of number of providers in the core,
241 // due to 1:1:1 pipeconf:driver:provider maybe find better way
242 final DriverProvider provider = new PiPipeconfDriverProviderInternal(
243 completeDriver);
244 // register the merged driver
245 driverAdminService.registerProvider(provider);
246 }
247
248 private void setDriverViaCfg(DeviceId deviceId, String driverName,
249 BasicDeviceConfig basicDeviceConfig) {
250 ObjectNode newCfg = (ObjectNode) basicDeviceConfig.node();
251 newCfg = newCfg.put(DRIVER, driverName);
252 ObjectMapper mapper = new ObjectMapper();
253 JsonNode newCfgNode = mapper.convertValue(newCfg, JsonNode.class);
254 cfgService.applyConfig(deviceId, BasicDeviceConfig.class, newCfgNode);
255 }
Andrea Campanellaf9c409a2017-07-13 14:14:41 +0200256
Andrea Campanellabc112a92017-06-26 19:06:43 +0200257 private class PiPipeconfDriverProviderInternal implements DriverProvider {
258
259 Driver driver;
260
261 PiPipeconfDriverProviderInternal(Driver driver) {
262 this.driver = driver;
263 }
264
265 @Override
266 public Set<Driver> getDrivers() {
267 return ImmutableSet.of(driver);
268 }
269 }
Andrea Campanellabc112a92017-06-26 19:06:43 +0200270}