org.onosproject.bmv2.ctl.Bmv2DeviceContextServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.onosproject.bmv2.ctl.Bmv2DeviceContextServiceImpl.java

Source

/*
 * Copyright 2016-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.bmv2.ctl;

import com.eclipsesource.json.Json;
import com.eclipsesource.json.JsonObject;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.google.common.collect.Maps;
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.KryoNamespace;
import org.onlab.util.SharedScheduledExecutors;
import org.onosproject.bmv2.api.context.Bmv2Configuration;
import org.onosproject.bmv2.api.context.Bmv2DefaultConfiguration;
import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
import org.onosproject.bmv2.api.context.Bmv2Interpreter;
import org.onosproject.bmv2.api.runtime.Bmv2DeviceAgent;
import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
import org.onosproject.bmv2.api.service.Bmv2Controller;
import org.onosproject.bmv2.api.service.Bmv2DeviceContextService;
import org.onosproject.mastership.MastershipService;
import org.onosproject.net.DeviceId;
import org.onosproject.net.device.DeviceService;
import org.onosproject.store.serializers.KryoNamespaces;
import org.onosproject.store.service.ConsistentMap;
import org.onosproject.store.service.ConsistentMapException;
import org.onosproject.store.service.MapEvent;
import org.onosproject.store.service.MapEventListener;
import org.onosproject.store.service.Serializer;
import org.onosproject.store.service.StorageService;
import org.onosproject.store.service.Versioned;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static com.google.common.base.Preconditions.checkNotNull;
import static org.onosproject.bmv2.api.context.Bmv2DefaultConfiguration.parse;
import static org.onosproject.store.service.MapEvent.Type.INSERT;
import static org.onosproject.store.service.MapEvent.Type.UPDATE;

@Component(immediate = true)
@Service
public class Bmv2DeviceContextServiceImpl implements Bmv2DeviceContextService {

    private static final String JSON_DEFAULT_CONFIG_PATH = "/default.json";
    private static final long CHECK_INTERVAL = 5_000; // milliseconds

    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
    private StorageService storageService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
    private DeviceService deviceService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
    private MastershipService mastershipService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
    private Bmv2Controller controller;

    private final ScheduledExecutorService scheduledExecutor = SharedScheduledExecutors.getPoolThreadExecutor();
    private final MapEventListener<DeviceId, Bmv2DeviceContext> contextListener = new ContextMapEventListener();
    private final ConcurrentMap<DeviceId, Lock> deviceLocks = Maps.newConcurrentMap();

    private ConsistentMap<DeviceId, Bmv2DeviceContext> contexts;
    private Map<String, ClassLoader> interpreterClassLoaders;
    private Bmv2DeviceContext defaultContext;
    private ScheduledFuture<?> configChecker = null;

    private final Logger log = LoggerFactory.getLogger(getClass());

    @Activate
    public void activate() {
        KryoNamespace kryo = new KryoNamespace.Builder().register(KryoNamespaces.API)
                .register(new BmvDeviceContextSerializer(), Bmv2DeviceContext.class).build();

        this.contexts = storageService.<DeviceId, Bmv2DeviceContext>consistentMapBuilder()
                .withSerializer(Serializer.using(kryo)).withName("onos-bmv2-contexts").build();

        Bmv2Configuration defaultConfiguration = loadDefaultConfiguration();
        Bmv2Interpreter defaultInterpreter = new Bmv2DefaultInterpreterImpl();
        defaultContext = new Bmv2DeviceContext(defaultConfiguration, defaultInterpreter);

        interpreterClassLoaders = Maps.newConcurrentMap();
        registerInterpreterClassLoader(defaultInterpreter.getClass(), this.getClass().getClassLoader());

        contexts.addListener(contextListener, scheduledExecutor);

        if (configChecker != null && configChecker.isCancelled()) {
            configChecker.cancel(false);
        }
        configChecker = scheduledExecutor.scheduleAtFixedRate(this::checkDevices, 0, CHECK_INTERVAL,
                TimeUnit.MILLISECONDS);

        log.info("Started");
    }

    @Deactivate
    public void deactivate() {
        contexts.removeListener(contextListener);
        if (configChecker != null) {
            configChecker.cancel(false);
        }
        log.info("Stopped");
    }

    @Override
    public Bmv2DeviceContext getContext(DeviceId deviceId) {
        checkNotNull(deviceId, "device id cannot be null");
        Versioned<Bmv2DeviceContext> versionedContext = contexts.get(deviceId);
        return (versionedContext == null) ? null : versionedContext.value();
    }

    @Override
    public void setContext(DeviceId deviceId, Bmv2DeviceContext context) {
        checkNotNull(deviceId, "device id cannot be null");
        checkNotNull(context, "context cannot be null");
        if (!interpreterClassLoaders.containsKey(context.interpreter().getClass().getName())) {
            log.error(
                    "Unable to set context, missing class loader for interpreter '{}'. "
                            + "Please register it with registerInterpreterClassLoader()",
                    context.interpreter().getClass().getName());
        } else {
            try {
                contexts.put(deviceId, context);
            } catch (ConsistentMapException.ConcurrentModification e) {
                log.error("Detected concurrent modification on context map");
            }
        }
    }

    @Override
    public void registerInterpreterClassLoader(Class<? extends Bmv2Interpreter> interpreterClass,
            ClassLoader loader) {
        interpreterClassLoaders.put(interpreterClass.getName(), loader);
    }

    @Override
    public Bmv2DeviceContext defaultContext() {
        return defaultContext;
    }

    @Override
    public void setDefaultContext(DeviceId deviceId) {
        Versioned<Bmv2DeviceContext> previous = contexts.put(deviceId, defaultContext);
        if (mastershipService.getMasterFor(deviceId) == null) {
            // Checking for who is the master here is ugly but necessary, as this method is called by Bmv2DeviceProvider
            // prior to master election. A solution could be to use a separate leadership contest instead of the
            // mastership service.
            triggerConfigCheck(deviceId, defaultContext);
        }
    }

    private void configCheck(DeviceId deviceId, Bmv2DeviceContext storedContext) {
        if (storedContext == null) {
            return;
        }
        // Synchronize executions over the same deviceId.
        Lock lock = deviceLocks.computeIfAbsent(deviceId, did -> new ReentrantLock());
        lock.lock();
        try {
            log.trace("Executing configuration check on {}...", deviceId);

            try {
                // FIXME: JSON dump is heavy, can we use the JSON MD5 to check the running configuration?
                String jsonString = controller.getAgent(deviceId).dumpJsonConfig();
                Bmv2Configuration deviceConfiguration = parse(Json.parse(jsonString).asObject());

                if (!storedContext.configuration().equals(deviceConfiguration)) {
                    log.info("Triggering configuration swap on {}...", deviceId);
                    try {
                        Bmv2DeviceAgent agent = controller.getAgent(deviceId);
                        String newJsonString = storedContext.configuration().json().toString();
                        agent.uploadNewJsonConfig(newJsonString);
                        agent.swapJsonConfig();
                    } catch (Bmv2RuntimeException e) {
                        log.error("Unable to swap configuration on {}: {}", deviceId, e.explain());
                    }
                }
            } catch (Bmv2RuntimeException e) {
                log.warn("Unable to dump JSON configuration from {}: {}", deviceId, e.explain());
            }
        } finally {
            lock.unlock();
        }
    }

    private void triggerConfigCheck(DeviceId deviceId, Bmv2DeviceContext context) {
        scheduledExecutor.schedule(() -> configCheck(deviceId, context), 0, TimeUnit.SECONDS);
    }

    private void checkDevices() {
        deviceService.getAvailableDevices().forEach(device -> {
            if (mastershipService.isLocalMaster(device.id())) {
                triggerConfigCheck(device.id(), getContext(device.id()));
            }
        });
    }

    protected static Bmv2DefaultConfiguration loadDefaultConfiguration() {
        try {
            JsonObject json = Json
                    .parse(new BufferedReader(new InputStreamReader(
                            Bmv2DeviceContextServiceImpl.class.getResourceAsStream(JSON_DEFAULT_CONFIG_PATH))))
                    .asObject();
            return parse(json);
        } catch (IOException e) {
            throw new RuntimeException("Unable to load default configuration", e);
        }
    }

    /**
     * Listener of context changes that immediately triggers config checks (to swap the config if necessary).
     */
    private class ContextMapEventListener implements MapEventListener<DeviceId, Bmv2DeviceContext> {
        @Override
        public void event(MapEvent<DeviceId, Bmv2DeviceContext> event) {
            DeviceId deviceId = event.key();
            if (event.type().equals(INSERT) || event.type().equals(UPDATE)) {
                if (mastershipService.isLocalMaster(deviceId)) {
                    log.trace("Context {} for {}", event.type().name(), deviceId);
                    triggerConfigCheck(deviceId, event.newValue().value());
                }
            }
        }
    }

    /**
     * Context serializer.
     */
    private class BmvDeviceContextSerializer extends com.esotericsoftware.kryo.Serializer<Bmv2DeviceContext> {

        @Override
        public void write(Kryo kryo, Output output, Bmv2DeviceContext context) {
            kryo.writeObject(output, context.configuration().json().toString());
            kryo.writeObject(output, context.interpreter().getClass().getName());
        }

        @Override
        public Bmv2DeviceContext read(Kryo kryo, Input input, Class<Bmv2DeviceContext> type) {
            String jsonStr = kryo.readObject(input, String.class);
            String interpreterClassName = kryo.readObject(input, String.class);
            Bmv2Configuration configuration = parse(Json.parse(jsonStr).asObject());
            ClassLoader loader = interpreterClassLoaders.get(interpreterClassName);
            if (loader == null) {
                throw new IllegalStateException(
                        "No class loader registered for interpreter: " + interpreterClassName);
            }
            try {
                Bmv2Interpreter interpreter = (Bmv2Interpreter) loader.loadClass(interpreterClassName)
                        .newInstance();
                return new Bmv2DeviceContext(configuration, interpreter);
            } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
                throw new RuntimeException("Unable to load interpreter class", e);
            }
        }
    }
}