Brian O'Connor

Initial implementation of the intent domain manager

Change-Id: I9721449599a4a67bfad7469173c3b47a681873f6
......@@ -70,7 +70,7 @@ import static org.onosproject.security.AppGuard.checkPermission;
/**
* An implementation of Intent Manager.
* An implementation of intent service.
*/
@Component(immediate = true)
@Service
......
......@@ -41,6 +41,10 @@
<artifactId>guava-testlib</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-incubator-api</artifactId>
</dependency>
</dependencies>
</project>
......
......@@ -45,6 +45,7 @@ import org.onosproject.core.DefaultApplication;
import org.onosproject.core.DefaultApplicationId;
import org.onosproject.core.DefaultGroupId;
import org.onosproject.core.Version;
import org.onosproject.incubator.net.domain.IntentDomainId;
import org.onosproject.mastership.MastershipTerm;
import org.onosproject.net.Annotations;
import org.onosproject.net.ChannelSpacing;
......@@ -416,7 +417,8 @@ public final class KryoNamespaces {
Frequency.class,
DefaultAnnotations.class,
PortStatistics.class,
DefaultPortStatistics.class
DefaultPortStatistics.class,
IntentDomainId.class
)
.register(new DefaultApplicationIdSerializer(), DefaultApplicationId.class)
.register(new URISerializer(), URI.class)
......
......@@ -16,8 +16,14 @@
package org.onosproject.incubator.net.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.annotations.Beta;
import com.google.common.collect.Lists;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import static com.google.common.base.Preconditions.checkNotNull;
......@@ -259,4 +265,39 @@ public abstract class Config<S> {
return this;
}
/**
* Gets the specified array property as a list of items.
*
* @param name property name
* @param function mapper from string to item
* @param <T> type of item
* @return list of items
*/
protected <T> List<T> getList(String name, Function<String, T> function) {
List<T> list = Lists.newArrayList();
ArrayNode arrayNode = (ArrayNode) node.path(name);
arrayNode.forEach(i -> list.add(function.apply(i.asText())));
return list;
}
/**
* Sets the specified property as an array of items in a given collection or
* clears it if null is given.
*
* @param name propertyName
* @param collection collection of items
* @param <T> type of items
* @return self
*/
protected <T> Config<S> setOrClear(String name, Collection<T> collection) {
if (collection == null) {
node.remove(name);
} else {
ArrayNode arrayNode = mapper.createArrayNode();
collection.forEach(i -> arrayNode.add(i.toString()));
node.set(name, arrayNode);
}
return this;
}
}
......
......@@ -18,6 +18,7 @@ package org.onosproject.incubator.net.config.basics;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.incubator.net.config.SubjectFactory;
import org.onosproject.incubator.net.domain.IntentDomainId;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.HostId;
......@@ -80,6 +81,14 @@ public final class SubjectFactories {
}
};
public static final SubjectFactory<IntentDomainId> INTENT_DOMAIN_SUBJECT_FACTORY =
new SubjectFactory<IntentDomainId>(IntentDomainId.class, "domains") {
@Override
public IntentDomainId createSubject(String key) {
return IntentDomainId.valueOf(key);
}
};
/**
* Provides reference to the core service, which is required for
* application subject factory.
......
......@@ -16,6 +16,7 @@
package org.onosproject.incubator.net.domain;
import com.google.common.annotations.Beta;
import com.google.common.base.MoreObjects;
import org.onlab.graph.Vertex;
import org.onosproject.net.DeviceId;
......@@ -30,23 +31,39 @@ public class DomainVertex implements Vertex {
// FIXME we will want to add a type enum or subclasses for the two different types
// A domain vertex is either an intent domain or a device:
private final IntentDomainId id;
private final IntentDomainId domainId;
// ----- or -----
private final DeviceId deviceId;
// Serialization constructor
private DomainVertex() {
this.id = null;
this.domainId = null;
this.deviceId = null;
}
DomainVertex(IntentDomainId id) {
this.id = checkNotNull(id, "Intent domain ID cannot be null.");
public DomainVertex(IntentDomainId id) {
this.domainId = checkNotNull(id, "Intent domain ID cannot be null.");
this.deviceId = null;
}
DomainVertex(DeviceId id) {
this.id = null;
public DomainVertex(DeviceId id) {
this.domainId = null;
this.deviceId = checkNotNull(id, "Device ID cannot be null.");
}
@Override
public String toString() {
if (domainId != null) {
return MoreObjects.toStringHelper(this)
.add("domainId", domainId)
.toString();
} else if (deviceId != null) {
return MoreObjects.toStringHelper(this)
.add("deviceId", deviceId)
.toString();
} else {
return MoreObjects.toStringHelper(this)
.toString();
}
}
}
......
......@@ -19,7 +19,6 @@ import com.google.common.annotations.Beta;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import java.util.Objects;
import java.util.Set;
/**
......@@ -37,7 +36,7 @@ public class IntentDomain {
private IntentDomainProvider provider;
IntentDomain(IntentDomainId id, String name,
public IntentDomain(IntentDomainId id, String name,
Set<DeviceId> internalDevices,
Set<ConnectPoint> edgePorts) {
this.id = id;
......@@ -88,7 +87,7 @@ public class IntentDomain {
*
* @return intent domain provider
*/
IntentDomainProvider provider() {
public IntentDomainProvider provider() {
return provider;
}
......@@ -115,16 +114,10 @@ public class IntentDomain {
}
/**
* Unsets the provider for the intent domain if the given provider matches
* the existing provider.
*
* @param provider provider to unset
* Unsets the provider for the intent domain.
*/
public void unsetProvider(IntentDomainProvider provider) {
// TODO consider checkState depending on caller
if (Objects.equals(this.provider, provider)) {
this.provider = null;
}
public void unsetProvider() {
this.provider = null;
}
//TODO add remaining setters (we will probably want to link this to the network config)
......
......@@ -22,7 +22,7 @@ import org.onosproject.core.ApplicationId;
* Administrative interface for the intent domain service.
*/
@Beta
public interface IntentDomainAdminService {
public interface IntentDomainAdminService extends IntentDomainService {
/**
* Register an application that provides intent domain service.
......
......@@ -16,6 +16,7 @@
package org.onosproject.incubator.net.domain;
import com.google.common.annotations.Beta;
import com.google.common.collect.ImmutableSet;
import org.onosproject.incubator.net.config.Config;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
......@@ -27,10 +28,13 @@ import java.util.Set;
* set of edge ports, and the application bound to control the domain.
*/
@Beta
public abstract class IntentDomainConfig extends Config<IntentDomainId> {
public class IntentDomainConfig extends Config<IntentDomainId> {
private static final String DOMAIN_NAME = "domainName";
private static final String DOMAIN_NAME = "name";
private static final String APPLICATION_NAME = "applicationName";
private static final String INTERNAL_DEVICES = "internalDevices";
private static final String EDGE_PORTS = "edgePorts";
/**
* Returns the friendly name for the domain.
......@@ -57,7 +61,7 @@ public abstract class IntentDomainConfig extends Config<IntentDomainId> {
* @return domain name
*/
public String applicationName() {
return get(APPLICATION_NAME, null); //TODO maybe not null?
return get(APPLICATION_NAME, "FIXME"); //TODO maybe not null?
}
/**
......@@ -70,8 +74,42 @@ public abstract class IntentDomainConfig extends Config<IntentDomainId> {
return (IntentDomainConfig) setOrClear(APPLICATION_NAME, applicationName);
}
//TODO sets
abstract Set<DeviceId> internalDevices();
abstract Set<ConnectPoint> edgePorts();
/**
* Returns the set of internal devices.
*
* @return set of internal devices
*/
public Set<DeviceId> internalDevices() {
return ImmutableSet.copyOf(getList(INTERNAL_DEVICES, DeviceId::deviceId));
}
/**
* Sets the set of internal devices.
*
* @param devices set of devices; null to clear
* @return self
*/
public IntentDomainConfig internalDevices(Set<DeviceId> devices) {
return (IntentDomainConfig) setOrClear(INTERNAL_DEVICES, devices);
}
/**
* Returns the set of edge ports.
*
* @return set of edge ports
*/
public Set<ConnectPoint> edgePorts() {
return ImmutableSet.copyOf(getList(EDGE_PORTS, ConnectPoint::deviceConnectPoint));
}
/**
* Sets the set of edge ports.
*
* @param connectPoints set of edge ports; null to clear
* @return self
*/
public IntentDomainConfig edgePorts(Set<ConnectPoint> connectPoints) {
return (IntentDomainConfig) setOrClear(EDGE_PORTS, connectPoints);
}
}
......
......@@ -26,7 +26,7 @@ public abstract class IntentPrimitive {
private final ApplicationId appId;
IntentPrimitive(ApplicationId appId) {
public IntentPrimitive(ApplicationId appId) {
this.appId = appId;
}
}
......
......@@ -26,7 +26,7 @@ import com.google.common.annotations.Beta;
* also be automatically cancelled by a provider after a short timeout.
*/
@Beta
class RequestContext {
public class RequestContext {
IntentDomain domain;
IntentResource resource;
//TODO other common parameters:
......
......@@ -29,6 +29,8 @@ import org.onosproject.incubator.net.config.basics.BasicHostConfig;
import org.onosproject.incubator.net.config.basics.BasicLinkConfig;
import org.onosproject.incubator.net.config.basics.BasicPortConfig;
import org.onosproject.incubator.net.config.basics.SubjectFactories;
import org.onosproject.incubator.net.domain.IntentDomainConfig;
import org.onosproject.incubator.net.domain.IntentDomainId;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.HostId;
......@@ -80,6 +82,14 @@ public class BasicNetworkConfigs {
public BasicLinkConfig createConfig() {
return new BasicLinkConfig();
}
},
new ConfigFactory<IntentDomainId, IntentDomainConfig>(INTENT_DOMAIN_SUBJECT_FACTORY,
IntentDomainConfig.class,
"basic") {
@Override
public IntentDomainConfig createConfig() {
return new IntentDomainConfig();
}
}
);
......
/*
* Copyright 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.incubator.net.domain.impl;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
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.graph.AdjacencyListsGraph;
import org.onlab.graph.Graph;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.incubator.net.config.NetworkConfigEvent;
import org.onosproject.incubator.net.config.NetworkConfigListener;
import org.onosproject.incubator.net.config.NetworkConfigService;
import org.onosproject.incubator.net.domain.DomainEdge;
import org.onosproject.incubator.net.domain.DomainVertex;
import org.onosproject.incubator.net.domain.IntentDomain;
import org.onosproject.incubator.net.domain.IntentDomainAdminService;
import org.onosproject.incubator.net.domain.IntentDomainConfig;
import org.onosproject.incubator.net.domain.IntentDomainId;
import org.onosproject.incubator.net.domain.IntentDomainListener;
import org.onosproject.incubator.net.domain.IntentDomainProvider;
import org.onosproject.incubator.net.domain.IntentDomainService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
/**
* Implementation of the intent domain service.
*/
@Component(immediate = true)
@Service
public class IntentDomainManager
implements IntentDomainService, IntentDomainAdminService {
private final Logger log = LoggerFactory.getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigService configService;
private NetworkConfigListener cfgListener = new InternalConfigListener();
private final ConcurrentMap<IntentDomainId, IntentDomain> domains = Maps.newConcurrentMap();
private final Multimap<String, IntentDomainId> appToDomain =
Multimaps.synchronizedSetMultimap(HashMultimap.<String, IntentDomainId>create());
private Graph<DomainVertex, DomainEdge> graph;
@Activate
protected void activate() {
configService.addListener(cfgListener);
configService.getSubjects(IntentDomainId.class, IntentDomainConfig.class)
.forEach(this::processConfig);
graph = buildGraph();
log.debug("Graph: {}", graph);
log.info("Started");
}
private void processConfig(IntentDomainId intentDomainId) {
IntentDomainConfig cfg = configService.getConfig(intentDomainId,
IntentDomainConfig.class);
domains.put(intentDomainId, createDomain(intentDomainId, cfg));
appToDomain.put(cfg.applicationName(), intentDomainId);
}
private IntentDomain createDomain(IntentDomainId id, IntentDomainConfig cfg) {
return new IntentDomain(id, cfg.domainName(), cfg.internalDevices(), cfg.edgePorts());
}
private Graph<DomainVertex, DomainEdge> buildGraph() {
Set<DomainVertex> vertices = Sets.newHashSet();
Set<DomainEdge> edges = Sets.newHashSet();
Map<DeviceId, DomainVertex> deviceVertices = Maps.newHashMap();
domains.forEach((id, domain) -> {
DomainVertex domainVertex = new DomainVertex(id);
// Add vertex for domain
vertices.add(domainVertex);
// Add vertices for connection devices
domain.edgePorts().stream()
.map(ConnectPoint::deviceId)
.collect(Collectors.toSet())
.forEach(did -> deviceVertices.putIfAbsent(did, new DomainVertex(did)));
// Add bi-directional edges between each domain and connection device
domain.edgePorts().forEach(cp -> {
DomainVertex deviceVertex = deviceVertices.get(cp.deviceId());
edges.add(new DomainEdge(domainVertex, deviceVertex, cp));
edges.add(new DomainEdge(deviceVertex, domainVertex, cp));
});
});
vertices.addAll(deviceVertices.values());
//FIXME verify graph integrity...
return new AdjacencyListsGraph<>(vertices, edges);
}
@Deactivate
protected void deactivate() {
configService.removeListener(cfgListener);
log.info("Stopped");
}
@Override
public IntentDomain getDomain(IntentDomainId id) {
return domains.get(id);
}
@Override
public Set<IntentDomain> getDomains() {
return ImmutableSet.copyOf(domains.values());
}
@Override
public Set<IntentDomain> getDomains(DeviceId deviceId) {
return domains.values().stream()
.filter(domain ->
domain.internalDevices().contains(deviceId) ||
domain.edgePorts().stream()
.map(ConnectPoint::deviceId)
.anyMatch(d -> d.equals(deviceId)))
.collect(Collectors.toSet());
}
@Override
public Graph<DomainVertex, DomainEdge> getDomainGraph() {
return graph;
}
@Override
public void addListener(IntentDomainListener listener) {
//TODO slide in AbstractListenerManager
}
@Override
public void removeListener(IntentDomainListener listener) {
//TODO slide in AbstractListenerManager
}
@Override
public void registerApplication(ApplicationId applicationId, IntentDomainProvider provider) {
appToDomain.get(applicationId.name()).forEach(d -> domains.get(d).setProvider(provider));
}
@Override
public void unregisterApplication(ApplicationId applicationId) {
appToDomain.get(applicationId.name()).forEach(d -> domains.get(d).unsetProvider());
}
private class InternalConfigListener implements NetworkConfigListener {
@Override
public void event(NetworkConfigEvent event) {
switch (event.type()) {
case CONFIG_ADDED:
case CONFIG_UPDATED:
processConfig((IntentDomainId) event.subject());
graph = buildGraph();
log.debug("Graph: {}", graph);
break;
case CONFIG_REGISTERED:
case CONFIG_UNREGISTERED:
case CONFIG_REMOVED:
default:
//TODO
break;
}
}
@Override
public boolean isRelevant(NetworkConfigEvent event) {
return event.configClass().equals(IntentDomainConfig.class);
}
}
}
{
"domains" : {
"cord" : {
"basic" : {
"name" : "Core Fabric",
"applicationName" : "org.onosproject.testdomain",
"internalDevices" : [ "of:1" ],
"edgePorts" : [ "of:12/1", "of:14/1" ]
}
},
"mpls" : {
"basic" : {
"name" : "MPLS Core",
"applicationName" : "org.onosproject.testdomain",
"internalDevices" : [ "of:2" ],
"edgePorts" : [ "of:12/2", "of:23/2" ]
}
},
"dc" : {
"basic" : {
"name" : "Data Center Fabric",
"applicationName" : "org.onosproject.testdomain",
"internalDevices" : [ "of:3" ],
"edgePorts" : [ "of:23/3", "of:34/3" ]
}
},
"optical" : {
"basic" : {
"name" : "Optical Core",
"applicationName" : "org.onosproject.testdomain",
"internalDevices" : [ "of:4" ],
"edgePorts" : [ "of:14/4", "of:34/4" ]
}
}
}
}
\ No newline at end of file