Hyunsun Moon
Committed by Gerrit Code Review

CORD-151 add initial skeleton for cord-vtn application

Change-Id: I57bf17445f1e571b51bca2fe7c2631e65cd43145
......@@ -36,6 +36,19 @@
</properties>
<dependencies>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-api</artifactId>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-core-serializers</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
......
/*
* Copyright 2014-2015 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.cordvtn;
/**
* Entity capable of handling a subject connected and disconnected situation.
*/
public interface ConnectionHandler<T> {
/**
* Processes the connected subject.
*
* @param subject subject
*/
void connected(T subject);
/**
* Processes the disconnected subject.
*
* @param subject subject.
*/
void disconnected(T subject);
}
/*
* Copyright 2014-2015 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.cordvtn;
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.packet.IpAddress;
import org.onlab.packet.TpPort;
import org.onlab.util.KryoNamespace;
import org.onosproject.cluster.ClusterService;
import org.onosproject.cluster.LeadershipEvent;
import org.onosproject.cluster.LeadershipEventListener;
import org.onosproject.cluster.LeadershipService;
import org.onosproject.cluster.NodeId;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.mastership.MastershipService;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
import org.onosproject.net.config.ConfigFactory;
import org.onosproject.net.config.NetworkConfigRegistry;
import org.onosproject.net.config.NetworkConfigService;
import org.onosproject.net.config.basics.SubjectFactories;
import org.onosproject.net.device.DeviceEvent;
import org.onosproject.net.device.DeviceListener;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.host.HostEvent;
import org.onosproject.net.host.HostListener;
import org.onosproject.net.host.HostService;
import org.onosproject.store.serializers.KryoNamespaces;
import org.onosproject.store.service.EventuallyConsistentMap;
import org.onosproject.store.service.LogicalClockService;
import org.onosproject.store.service.StorageService;
import org.slf4j.Logger;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import static org.onlab.util.Tools.groupedThreads;
import static org.onosproject.cordvtn.OvsdbNode.State.INIT;
import static org.slf4j.LoggerFactory.getLogger;
/**
* CORD VTN Application that provisions overlay virtual tenant networks.
*/
@Component(immediate = true)
@Service
public class CordVtn implements CordVtnService {
protected final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected StorageService storageService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected LogicalClockService clockService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ClusterService clusterService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected LeadershipService leadershipService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigService configService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigRegistry configRegistry;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostService hostService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected MastershipService mastershipService;
private static final int DEFAULT_NUM_THREADS = 1;
private static final KryoNamespace.Builder NODE_SERIALIZER = KryoNamespace.newBuilder()
.register(KryoNamespaces.API)
.register(OvsdbNode.class);
private final ExecutorService eventExecutor = Executors.newFixedThreadPool(
DEFAULT_NUM_THREADS, groupedThreads("onos/cordvtn", "event-handler"));
private final LeadershipEventListener leadershipListener = new InternalLeadershipListener();
private final DeviceListener deviceListener = new InternalDeviceListener();
private final HostListener hostListener = new InternalHostListener();
private final NodeHandler nodeHandler = new NodeHandler();
private final BridgeHandler bridgeHandler = new BridgeHandler();
private final VirtualMachineHandler vmHandler = new VirtualMachineHandler();
private final ConfigFactory configFactory =
new ConfigFactory(SubjectFactories.APP_SUBJECT_FACTORY, CordVtnConfig.class, "cordvtn") {
@Override
public CordVtnConfig createConfig() {
return new CordVtnConfig();
}
};
private ApplicationId appId;
private NodeId local;
private EventuallyConsistentMap<DeviceId, OvsdbNode> nodeStore;
private NodeConnectionManager nodeConnectionManager;
@Activate
protected void activate() {
appId = coreService.registerApplication("org.onosproject.cordvtn");
local = clusterService.getLocalNode().id();
nodeStore = storageService.<DeviceId, OvsdbNode>eventuallyConsistentMapBuilder()
.withName("cordvtn-nodestore")
.withSerializer(NODE_SERIALIZER)
.withTimestampProvider((k, v) -> clockService.getTimestamp())
.build();
configRegistry.registerConfigFactory(configFactory);
deviceService.addListener(deviceListener);
hostService.addListener(hostListener);
leadershipService.addListener(leadershipListener);
leadershipService.runForLeadership(appId.name());
nodeConnectionManager = new NodeConnectionManager(appId, local, nodeStore,
mastershipService, leadershipService);
nodeConnectionManager.start();
log.info("Started");
}
@Deactivate
protected void deactivate() {
nodeConnectionManager.stop();
leadershipService.removeListener(leadershipListener);
leadershipService.withdraw(appId.name());
deviceService.removeListener(deviceListener);
hostService.removeListener(hostListener);
eventExecutor.shutdown();
nodeStore.destroy();
configRegistry.unregisterConfigFactory(configFactory);
log.info("Stopped");
}
@Override
public void addNode(String hostname, IpAddress ip, TpPort port) {
DefaultOvsdbNode node = new DefaultOvsdbNode(hostname, ip, port, DeviceId.NONE, INIT);
if (nodeStore.containsKey(node.deviceId())) {
log.warn("Node {} with ovsdb-server {}:{} already exists", hostname, ip, port);
return;
}
nodeStore.put(node.deviceId(), node);
log.info("New node {} with ovsdb-server {}:{} has been added", hostname, ip, port);
}
@Override
public void deleteNode(IpAddress ip, TpPort port) {
DeviceId deviceId = DeviceId.deviceId("ovsdb:" + ip + ":" + port);
OvsdbNode node = nodeStore.get(deviceId);
if (node == null) {
log.warn("Node with ovsdb-server on {}:{} does not exist", ip, port);
return;
}
nodeConnectionManager.disconnectNode(node);
nodeStore.remove(node.deviceId());
}
@Override
public int getNodeCount() {
return nodeStore.size();
}
@Override
public List<OvsdbNode> getNodes() {
return nodeStore.values()
.stream()
.collect(Collectors.toList());
}
private void initialSetup() {
// Read ovsdb nodes from network config
CordVtnConfig config = configService.getConfig(appId, CordVtnConfig.class);
if (config == null) {
log.warn("No configuration found");
return;
}
config.ovsdbNodes().forEach(
node -> addNode(node.hostname(), node.ip(), node.port()));
}
private synchronized void processLeadershipChange(NodeId leader) {
// Only the leader performs the initial setup
if (leader == null || !leader.equals(local)) {
return;
}
initialSetup();
}
private class InternalLeadershipListener implements LeadershipEventListener {
@Override
public void event(LeadershipEvent event) {
if (event.subject().topic().equals(appId.name())) {
processLeadershipChange(event.subject().leader());
}
}
}
private class InternalDeviceListener implements DeviceListener {
@Override
public void event(DeviceEvent event) {
Device device = event.subject();
ConnectionHandler handler =
(device.type() == Device.Type.CONTROLLER ? nodeHandler : bridgeHandler);
switch (event.type()) {
case DEVICE_ADDED:
eventExecutor.submit(() -> handler.connected(device));
break;
case DEVICE_AVAILABILITY_CHANGED:
eventExecutor.submit(() -> handler.disconnected(device));
break;
default:
break;
}
}
}
private class InternalHostListener implements HostListener {
@Override
public void event(HostEvent event) {
Host vm = event.subject();
switch (event.type()) {
case HOST_ADDED:
eventExecutor.submit(() -> vmHandler.connected(vm));
break;
case HOST_REMOVED:
eventExecutor.submit(() -> vmHandler.disconnected(vm));
break;
default:
break;
}
}
}
private class NodeHandler implements ConnectionHandler<Device> {
@Override
public void connected(Device device) {
// create bridge and set bridgeId
// set node state connected
}
@Override
public void disconnected(Device device) {
// set node state disconnected if the node exists
// which means that the node is not deleted explicitly
}
}
private class BridgeHandler implements ConnectionHandler<Device> {
@Override
public void connected(Device device) {
// create vxlan port
}
@Override
public void disconnected(Device device) {
}
}
private class VirtualMachineHandler implements ConnectionHandler<Host> {
@Override
public void connected(Host host) {
// install flow rules for this vm
}
@Override
public void disconnected(Host host) {
// uninstall flow rules associated with this vm
}
}
}
/*
* Copyright 2014-2015 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.cordvtn;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.Sets;
import org.onlab.packet.IpAddress;
import org.onlab.packet.TpPort;
import org.onosproject.core.ApplicationId;
import org.onosproject.net.config.Config;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Configuration object for CORD VTN service.
*/
public class CordVtnConfig extends Config<ApplicationId> {
public static final String OVSDB_NODES = "ovsdbNodes";
public static final String HOSTNAME = "hostname";
public static final String IP = "ip";
public static final String PORT = "port";
/**
* Returns the set of ovsdb nodes read from network config.
*
* @return set of OvsdbNodeConfig or null
*/
public Set<OvsdbNodeConfig> ovsdbNodes() {
Set<OvsdbNodeConfig> ovsdbNodes = Sets.newHashSet();
JsonNode nodes = object.get(OVSDB_NODES);
if (nodes == null) {
return null;
}
nodes.forEach(jsonNode -> ovsdbNodes.add(new OvsdbNodeConfig(
jsonNode.path(HOSTNAME).asText(),
IpAddress.valueOf(jsonNode.path(IP).asText()),
TpPort.tpPort(jsonNode.path(PORT).asInt()))));
return ovsdbNodes;
}
/**
* Configuration for an OVSDB node.
*/
public static class OvsdbNodeConfig {
private final String hostname;
private final IpAddress ip;
private final TpPort port;
public OvsdbNodeConfig(String hostname, IpAddress ip, TpPort port) {
this.hostname = checkNotNull(hostname);
this.ip = checkNotNull(ip);
this.port = checkNotNull(port);
}
/**
* Returns hostname of the node.
*
* @return hostname
*/
public String hostname() {
return this.hostname;
}
/**
* Returns ip address to access ovsdb-server of the node.
*
* @return ip address
*/
public IpAddress ip() {
return this.ip;
}
/**
* Returns port number to access ovsdb-server of the node.
*
* @return port number
*/
public TpPort port() {
return this.port;
}
}
}
......@@ -15,6 +15,9 @@
*/
package org.onosproject.cordvtn;
import org.onlab.packet.IpAddress;
import org.onlab.packet.TpPort;
import java.util.List;
/**
......@@ -22,19 +25,21 @@ import java.util.List;
*/
public interface CordVtnService {
/**
* Adds new nodes to the service and processes initial setup.
* Adds a new node to the service.
*
* @param ovsdbNodes list of nodes
* @param hostname hostname of the node
* @param ip ip address to access the ovsdb server running on the node
* @param port port number to access the ovsdb server running on the node
*/
void addNodes(List<OvsdbNode> ovsdbNodes);
void addNode(String hostname, IpAddress ip, TpPort port);
/**
* Deletes the nodes from the service and cleans up unnecessary configurations
* associated with the deleted nodes.
* Deletes the node from the service.
*
* @param ovsdbNodes list of nodes
* @param ip ip address to access the ovsdb server running on the node
* @param port port number to access the ovsdb server running on the node
*/
void deleteNodes(List<OvsdbNode> ovsdbNodes);
void deleteNode(IpAddress ip, TpPort port);
/**
* Returns the number of the nodes known to the service.
......
/*
* Copyright 2014-2015 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.cordvtn;
import org.onlab.packet.IpAddress;
import org.onlab.packet.TpPort;
import org.onosproject.net.DeviceId;
import java.util.Objects;
/**
* OvsdbNode implementation.
*/
public class DefaultOvsdbNode implements OvsdbNode {
private final String hostname;
private final IpAddress ip;
private final TpPort port;
private final DeviceId deviceId;
private final DeviceId bridgeId;
private final State state;
public DefaultOvsdbNode(String hostname, IpAddress ip, TpPort port,
DeviceId bridgeId, State state) {
this.hostname = hostname;
this.ip = ip;
this.port = port;
this.deviceId = DeviceId.deviceId(
"ovsdb:" + ip.toString() + ":" + port.toString());
this.bridgeId = bridgeId;
this.state = state;
}
@Override
public IpAddress ip() {
return this.ip;
}
@Override
public TpPort port() {
return this.port;
}
@Override
public String hostname() {
return this.hostname;
}
@Override
public State state() {
return this.state;
}
@Override
public DeviceId deviceId() {
return this.deviceId;
}
@Override
public DeviceId bridgeId() {
return this.bridgeId;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof DefaultOvsdbNode) {
DefaultOvsdbNode that = (DefaultOvsdbNode) o;
// We compare the ip and port only.
if (this.ip.equals(that.ip) && this.port.equals(that.port)) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(ip, port);
}
}
/*
* Copyright 2014-2015 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.cordvtn;
import org.onosproject.cluster.LeadershipService;
import org.onosproject.cluster.NodeId;
import org.onosproject.core.ApplicationId;
import org.onosproject.mastership.MastershipService;
import org.onosproject.net.DeviceId;
import org.onosproject.store.service.EventuallyConsistentMap;
import org.slf4j.Logger;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static org.onlab.util.Tools.groupedThreads;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Node connection manager.
*/
public class NodeConnectionManager {
protected final Logger log = getLogger(getClass());
private final ApplicationId appId;
private final NodeId localId;
private final EventuallyConsistentMap<DeviceId, OvsdbNode> nodeStore;
private final MastershipService mastershipService;
private final LeadershipService leadershipService;
private static final int DELAY_SEC = 5;
private ScheduledExecutorService connectionExecutor;
/**
* Creates a new NodeConnectionManager.
*
* @param localId local id
* @param nodeStore node store
* @param mastershipService mastership service
*/
public NodeConnectionManager(ApplicationId appId, NodeId localId,
EventuallyConsistentMap<DeviceId, OvsdbNode> nodeStore,
MastershipService mastershipService,
LeadershipService leadershipService) {
this.appId = appId;
this.localId = localId;
this.nodeStore = nodeStore;
this.mastershipService = mastershipService;
this.leadershipService = leadershipService;
}
/**
* Starts the node connection manager.
*/
public void start() {
connectionExecutor = Executors.newSingleThreadScheduledExecutor(
groupedThreads("onos/cordvtn", "connection-executor"));
connectionExecutor.scheduleWithFixedDelay(() -> nodeStore.values()
.stream()
.filter(node -> localId.equals(getMaster(node)))
.forEach(node -> connectNode(node)), 0, DELAY_SEC, TimeUnit.SECONDS);
}
/**
* Stops the node connection manager.
*/
public void stop() {
connectionExecutor.shutdown();
}
/**
* Adds a new node to the system.
*
* @param ovsdbNode ovsdb node
*/
public void connectNode(OvsdbNode ovsdbNode) {
switch (ovsdbNode.state()) {
case INIT:
case DISCONNECTED:
// TODO: set the node to passive mode
case READY:
// TODO: initiate connection
break;
case CONNECTED:
break;
default:
}
}
/**
* Deletes the ovsdb node.
*
* @param ovsdbNode ovsdb node
*/
public void disconnectNode(OvsdbNode ovsdbNode) {
switch (ovsdbNode.state()) {
case CONNECTED:
// TODO: disconnect
break;
case INIT:
case READY:
case DISCONNECTED:
break;
default:
}
}
private NodeId getMaster(OvsdbNode ovsdbNode) {
// Return the master of the bridge(switch) if it exist or
// return the current leader
if (ovsdbNode.bridgeId() == DeviceId.NONE) {
return leadershipService.getLeader(this.appId.name());
} else {
return mastershipService.getMasterFor(ovsdbNode.bridgeId());
}
}
private void setPassiveMode(OvsdbNode ovsdbNode) {
// TODO: need ovsdb client implementation first
// TODO: set the remove ovsdb server passive mode
// TODO: set the node state READY if it succeed
}
private void connect(OvsdbNode ovsdbNode) {
// TODO: need ovsdb client implementation first
}
private void disconnect(OvsdbNode ovsdbNode) {
// TODO: need ovsdb client implementation first
}
}
......@@ -18,8 +18,6 @@ package org.onosproject.cordvtn;
import org.onlab.packet.IpAddress;
import org.onlab.packet.TpPort;
import org.onosproject.net.DeviceId;
import org.onosproject.net.behaviour.BridgeConfig;
import org.onosproject.net.behaviour.TunnelConfig;
/**
* Representation of a node with ovsdb server.
......@@ -29,7 +27,7 @@ public interface OvsdbNode {
* State of the ovsdb node.
*/
enum State {
READY, CONNECTED, DISCONNECTED
INIT, READY, CONNECTED, DISCONNECTED
}
/**
......@@ -47,44 +45,30 @@ public interface OvsdbNode {
TpPort port();
/**
* Returns the state of the node.
* Returns the hostname of the node.
*
* @return state of the node
* @return hostname
*/
State getState();
String hostname();
/**
* Sets the state of the node.
* Returns the state of the node.
*
* @param state state of the node
* @return state of the node
*/
void setState(State state);
State state();
/**
* Returns the device ID of the node.
*
* @return device id
*/
DeviceId getDeviceId();
DeviceId deviceId();
/**
* Sets the device id of the node.
* Returns the device ID of the bridge associated with this node.
*
* @param deviceId device identifier
*/
void setDeviceId(DeviceId deviceId);
/**
* Returns the bridge configuration handler of the node.
*
* @return bridge config behavior instance
*/
BridgeConfig getBridgeConfig();
/**
* Returns the tunnel configuration handler of the node.
*
* @return tunnel config behavior instance
* @return device id
*/
TunnelConfig getTunnelConfig();
DeviceId bridgeId();
}
......