blob: 1e66d7f7bf9860e1e614ad10282af3567b12f4cf [file] [log] [blame]
/*
* Copyright 2017-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.net.pi.impl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableSet;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.onlab.util.ItemNotFoundException;
import org.onosproject.net.DeviceId;
import org.onosproject.net.config.ConfigFactory;
import org.onosproject.net.config.NetworkConfigEvent;
import org.onosproject.net.config.NetworkConfigListener;
import org.onosproject.net.config.NetworkConfigRegistry;
import org.onosproject.net.config.basics.BasicDeviceConfig;
import org.onosproject.net.config.basics.SubjectFactories;
import org.onosproject.net.driver.Behaviour;
import org.onosproject.net.driver.DefaultDriver;
import org.onosproject.net.driver.Driver;
import org.onosproject.net.driver.DriverAdminService;
import org.onosproject.net.driver.DriverProvider;
import org.onosproject.net.driver.DriverService;
import org.onosproject.net.pi.model.PiPipeconf;
import org.onosproject.net.pi.model.PiPipeconfId;
import org.onosproject.net.pi.runtime.PiPipeconfConfig;
import org.onosproject.net.pi.runtime.PiPipeconfService;
import org.slf4j.Logger;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static org.onlab.util.Tools.groupedThreads;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Implementation of the PiPipeconfService.
*/
@Component(immediate = true)
@Service
public class PiPipeconfServiceImpl implements PiPipeconfService {
private final Logger log = getLogger(getClass());
private static final String DRIVER = "driver";
private static final String CFG_SCHEME = "piPipeconf";
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigRegistry cfgService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DriverService driverService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DriverAdminService driverAdminService;
//TODO move to replicated map
protected ConcurrentHashMap<PiPipeconfId, PiPipeconf> piPipeconfs = new ConcurrentHashMap<>();
//TODO move to replicated map
protected ConcurrentHashMap<DeviceId, PiPipeconfId> devicesToPipeconf = new ConcurrentHashMap<>();
protected ExecutorService executor =
Executors.newFixedThreadPool(5, groupedThreads("onos/pipipeconfservice",
"pipeline-to-device-%d", log));
protected final ConfigFactory factory =
new ConfigFactory<DeviceId, PiPipeconfConfig>(
SubjectFactories.DEVICE_SUBJECT_FACTORY,
PiPipeconfConfig.class, CFG_SCHEME) {
@Override
public PiPipeconfConfig createConfig() {
return new PiPipeconfConfig();
}
};
protected final NetworkConfigListener cfgListener = new InternalNetworkConfigListener();
@Activate
public void activate() {
cfgService.registerConfigFactory(factory);
cfgService.addListener(cfgListener);
cfgService.getSubjects(DeviceId.class, PiPipeconfConfig.class)
.forEach(this::addPipeconfFromCfg);
log.info("Started");
}
@Deactivate
public void deactivate() {
executor.shutdown();
cfgService.removeListener(cfgListener);
cfgService.unregisterConfigFactory(factory);
piPipeconfs.clear();
devicesToPipeconf.clear();
cfgService = null;
driverAdminService = null;
driverService = null;
log.info("Stopped");
}
@Override
public void register(PiPipeconf pipeconf) throws IllegalStateException {
log.warn("Currently using local maps, needs to be moved to a distributed store");
piPipeconfs.put(pipeconf.id(), pipeconf);
}
@Override
public Iterable<PiPipeconf> getPipeconfs() {
throw new UnsupportedOperationException("Currently unsupported");
}
@Override
public Optional<PiPipeconf> getPipeconf(PiPipeconfId id) {
return Optional.ofNullable(piPipeconfs.get(id));
}
@Override
public CompletableFuture<Boolean> bindToDevice(PiPipeconfId pipeconfId, DeviceId deviceId) {
CompletableFuture<Boolean> operationResult = new CompletableFuture<>();
executor.execute(() -> {
BasicDeviceConfig basicDeviceConfig =
cfgService.getConfig(deviceId, BasicDeviceConfig.class);
Driver baseDriver = driverService.getDriver(basicDeviceConfig.driver());
String completeDriverName = baseDriver.name() + ":" + pipeconfId;
PiPipeconf piPipeconf = piPipeconfs.get(pipeconfId);
if (piPipeconf == null) {
log.warn("Pipeconf {} is not present", pipeconfId);
operationResult.complete(false);
} else {
//if driver exists already we don't create a new one.
//needs to be done via exception catching due to DriverRegistry throwing it on a null return from
//the driver map.
try {
driverService.getDriver(completeDriverName);
} catch (ItemNotFoundException e) {
log.debug("First time pipeconf {} is used with base driver {}, merging the two",
pipeconfId, baseDriver);
//extract the behaviours from the pipipeconf.
Map<Class<? extends Behaviour>, Class<? extends Behaviour>> behaviours = new HashMap<>();
piPipeconf.behaviours().forEach(b -> {
behaviours.put(b, piPipeconf.implementation(b).get());
});
Driver piPipeconfDriver = new DefaultDriver(completeDriverName, baseDriver.parents(),
baseDriver.manufacturer(), baseDriver.hwVersion(), baseDriver.swVersion(),
behaviours, new HashMap<>());
//we take the base driver created with the behaviours of the PiPeconf and
// merge it with the base driver that was assigned to the device
Driver completeDriver = piPipeconfDriver.merge(baseDriver);
//This might lead to explosion of number of providers in the core,
// due to 1:1:1 pipeconf:driver:provider maybe find better way
DriverProvider provider = new PiPipeconfDriverProviderInternal(completeDriver);
//we register to the dirver susbystem the driver provider containing the merged driver
driverAdminService.registerProvider(provider);
}
//Changing the configuration for the device to enforce the full driver with pipipeconf
// and base behaviours
ObjectNode newCfg = (ObjectNode) basicDeviceConfig.node();
newCfg = newCfg.put(DRIVER, completeDriverName);
ObjectMapper mapper = new ObjectMapper();
JsonNode newCfgNode = mapper.convertValue(newCfg, JsonNode.class);
cfgService.applyConfig(deviceId, BasicDeviceConfig.class, newCfgNode);
//Completable future is needed for when this method will also apply the pipeline to the device.
operationResult.complete(true);
}
});
return operationResult;
}
@Override
public Optional<PiPipeconfId> ofDevice(DeviceId deviceId) {
return Optional.ofNullable(devicesToPipeconf.get(deviceId));
}
private class PiPipeconfDriverProviderInternal implements DriverProvider {
Driver driver;
PiPipeconfDriverProviderInternal(Driver driver) {
this.driver = driver;
}
@Override
public Set<Driver> getDrivers() {
return ImmutableSet.of(driver);
}
}
private void addPipeconfFromCfg(DeviceId deviceId) {
PiPipeconfConfig pipeconfConfig =
cfgService.getConfig(deviceId, PiPipeconfConfig.class);
PiPipeconfId id = pipeconfConfig.piPipeconfId();
if (id.id().equals("")) {
log.warn("Not adding empty pipeconfId for device {}", deviceId);
} else {
devicesToPipeconf.put(deviceId, pipeconfConfig.piPipeconfId());
}
}
/**
* Listener for configuration events.
*/
private class InternalNetworkConfigListener implements NetworkConfigListener {
@Override
public void event(NetworkConfigEvent event) {
DeviceId deviceId = (DeviceId) event.subject();
addPipeconfFromCfg(deviceId);
}
@Override
public boolean isRelevant(NetworkConfigEvent event) {
return event.configClass().equals(PiPipeconfConfig.class) &&
(event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED);
}
}
}