diff options
Diffstat (limited to 'framework/src/onos/core/store')
29 files changed, 1654 insertions, 563 deletions
diff --git a/framework/src/onos/core/store/dist/pom.xml b/framework/src/onos/core/store/dist/pom.xml index cc293da4..f2ec2a71 100644 --- a/framework/src/onos/core/store/dist/pom.xml +++ b/framework/src/onos/core/store/dist/pom.xml @@ -71,7 +71,7 @@ <dependency> <groupId>org.mapdb</groupId> <artifactId>mapdb</artifactId> - <version>1.0.7</version> + <version>1.0.8</version> </dependency> <dependency> diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinition.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinition.java deleted file mode 100644 index 75f05a31..00000000 --- a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinition.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.store.cluster.impl; - -import java.util.Set; - -import com.google.common.collect.ImmutableSet; - -/** - * Cluster definition. - */ -public class ClusterDefinition { - - private Set<NodeInfo> nodes; - private String ipPrefix; - - /** - * Creates a new cluster definition. - * @param nodes cluster nodes information - * @param ipPrefix ip prefix common to all cluster nodes - * @return cluster definition - */ - public static ClusterDefinition from(Set<NodeInfo> nodes, String ipPrefix) { - ClusterDefinition definition = new ClusterDefinition(); - definition.ipPrefix = ipPrefix; - definition.nodes = ImmutableSet.copyOf(nodes); - return definition; - } - - /** - * Returns set of cluster nodes info. - * @return cluster nodes info - */ - public Set<NodeInfo> getNodes() { - return ImmutableSet.copyOf(nodes); - } - - /** - * Returns ipPrefix in dotted decimal notion. - * @return ip prefix - */ - public String getIpPrefix() { - return ipPrefix; - } -}
\ No newline at end of file diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinitionManager.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinitionManager.java deleted file mode 100644 index 8b0001d8..00000000 --- a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinitionManager.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * 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.store.cluster.impl; - -import com.google.common.collect.ImmutableSet; -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.Service; -import org.onlab.packet.IpAddress; -import org.onosproject.cluster.ClusterDefinitionService; -import org.onosproject.cluster.ControllerNode; -import org.onosproject.cluster.DefaultControllerNode; -import org.onosproject.cluster.NodeId; -import org.onosproject.store.consistent.impl.DatabaseDefinition; -import org.onosproject.store.consistent.impl.DatabaseDefinitionStore; -import org.slf4j.Logger; - -import java.io.File; -import java.io.IOException; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.util.Enumeration; -import java.util.Set; -import java.util.stream.Collectors; - -import static java.net.NetworkInterface.getNetworkInterfaces; -import static java.util.Collections.list; -import static org.onosproject.cluster.DefaultControllerNode.DEFAULT_PORT; -import static org.onosproject.store.consistent.impl.DatabaseManager.PARTITION_DEFINITION_FILE; -import static org.slf4j.LoggerFactory.getLogger; - -/** - * Implementation of ClusterDefinitionService. - */ -@Component(immediate = true) -@Service -public class ClusterDefinitionManager implements ClusterDefinitionService { - - public static final String CLUSTER_DEFINITION_FILE = "../config/cluster.json"; - private static final String ONOS_NIC = "ONOS_NIC"; - private static final Logger log = getLogger(ClusterDefinitionManager.class); - private ControllerNode localNode; - private Set<ControllerNode> seedNodes; - - @Activate - public void activate() { - File clusterDefinitionFile = new File(CLUSTER_DEFINITION_FILE); - ClusterDefinitionStore clusterDefinitionStore = - new ClusterDefinitionStore(clusterDefinitionFile.getPath()); - - if (!clusterDefinitionFile.exists()) { - createDefaultClusterDefinition(clusterDefinitionStore); - } - - try { - ClusterDefinition clusterDefinition = clusterDefinitionStore.read(); - establishSelfIdentity(clusterDefinition); - seedNodes = ImmutableSet - .copyOf(clusterDefinition.getNodes()) - .stream() - .filter(n -> !localNode.id().equals(new NodeId(n.getId()))) - .map(n -> new DefaultControllerNode(new NodeId(n.getId()), - IpAddress.valueOf(n.getIp()), - n.getTcpPort())) - .collect(Collectors.toSet()); - } catch (IOException e) { - throw new IllegalStateException("Failed to read cluster definition.", e); - } - - log.info("Started"); - } - - @Deactivate - public void deactivate() { - log.info("Stopped"); - } - - @Override - public ControllerNode localNode() { - return localNode; - } - - @Override - public Set<ControllerNode> seedNodes() { - return seedNodes; - } - - @Override - public void formCluster(Set<ControllerNode> nodes, String ipPrefix) { - try { - Set<NodeInfo> infos = Sets.newHashSet(); - nodes.forEach(n -> infos.add(NodeInfo.from(n.id().toString(), - n.ip().toString(), - n.tcpPort()))); - - ClusterDefinition cdef = ClusterDefinition.from(infos, ipPrefix); - new ClusterDefinitionStore(CLUSTER_DEFINITION_FILE).write(cdef); - - DatabaseDefinition ddef = DatabaseDefinition.from(infos); - new DatabaseDefinitionStore(PARTITION_DEFINITION_FILE).write(ddef); - } catch (IOException e) { - log.error("Unable to form cluster", e); - } - } - - private IpAddress findLocalIp(ClusterDefinition clusterDefinition) throws SocketException { - Enumeration<NetworkInterface> interfaces = - NetworkInterface.getNetworkInterfaces(); - while (interfaces.hasMoreElements()) { - NetworkInterface iface = interfaces.nextElement(); - Enumeration<InetAddress> inetAddresses = iface.getInetAddresses(); - while (inetAddresses.hasMoreElements()) { - IpAddress ip = IpAddress.valueOf(inetAddresses.nextElement()); - if (clusterDefinition.getNodes().stream() - .map(NodeInfo::getIp) - .map(IpAddress::valueOf) - .anyMatch(nodeIp -> ip.equals(nodeIp))) { - return ip; - } - } - } - throw new IllegalStateException("Unable to determine local ip"); - } - - private void establishSelfIdentity(ClusterDefinition clusterDefinition) { - try { - IpAddress ip = findLocalIp(clusterDefinition); - localNode = new DefaultControllerNode(new NodeId(ip.toString()), ip); - } catch (SocketException e) { - throw new IllegalStateException("Cannot determine local IP", e); - } - } - - private void createDefaultClusterDefinition(ClusterDefinitionStore store) { - // Assumes IPv4 is returned. - String ip = getSiteLocalAddress(); - String ipPrefix = ip.replaceFirst("\\.[0-9]*$", ".*"); - NodeInfo node = NodeInfo.from(ip, ip, DEFAULT_PORT); - try { - store.write(ClusterDefinition.from(ImmutableSet.of(node), ipPrefix)); - } catch (IOException e) { - log.warn("Unable to write default cluster definition", e); - } - } - - /** - * Returns the address that matches the IP prefix given in ONOS_NIC - * environment variable if one was specified, or the first site local - * address if one can be found or the loopback address otherwise. - * - * @return site-local address in string form - */ - public static String getSiteLocalAddress() { - try { - String ipPrefix = System.getenv(ONOS_NIC); - for (NetworkInterface nif : list(getNetworkInterfaces())) { - for (InetAddress address : list(nif.getInetAddresses())) { - IpAddress ip = IpAddress.valueOf(address); - if (ipPrefix == null && address.isSiteLocalAddress() || - ipPrefix != null && matchInterface(ip.toString(), ipPrefix)) { - return ip.toString(); - } - } - } - } catch (SocketException e) { - log.error("Unable to get network interfaces", e); - } - - return IpAddress.valueOf(InetAddress.getLoopbackAddress()).toString(); - } - - // Indicates whether the specified interface address matches the given prefix. - // FIXME: Add a facility to IpPrefix to make this more robust - private static boolean matchInterface(String ip, String ipPrefix) { - String s = ipPrefix.replaceAll("\\.\\*", ""); - return ip.startsWith(s); - } -} diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinitionStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinitionStore.java deleted file mode 100644 index 2a2f4dc4..00000000 --- a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinitionStore.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.store.cluster.impl; - -import static com.google.common.base.Preconditions.checkNotNull; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.io.Files; - -import java.io.File; -import java.io.IOException; - -/** - * Allows for reading and writing cluster definition as a JSON file. - */ -public class ClusterDefinitionStore { - - private final File file; - - /** - * Creates a reader/writer of the cluster definition file. - * @param filePath location of the definition file - */ - public ClusterDefinitionStore(String filePath) { - file = new File(filePath); - } - - /** - * Returns the cluster definition. - * @return cluster definition - * @throws IOException when I/O exception of some sort has occurred - */ - public ClusterDefinition read() throws IOException { - ObjectMapper mapper = new ObjectMapper(); - return mapper.readValue(file, ClusterDefinition.class); - } - - /** - * Writes the specified cluster definition to file. - * @param definition cluster definition - * @throws IOException when I/O exception of some sort has occurred - */ - public void write(ClusterDefinition definition) throws IOException { - checkNotNull(definition); - // write back to file - Files.createParentDirs(file); - ObjectMapper mapper = new ObjectMapper(); - mapper.writeValue(file, definition); - } -} diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/DistributedClusterStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/DistributedClusterStore.java index 859efebf..3bb6a708 100644 --- a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/DistributedClusterStore.java +++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/DistributedClusterStore.java @@ -27,8 +27,8 @@ import org.apache.felix.scr.annotations.Service; import org.joda.time.DateTime; import org.onlab.packet.IpAddress; import org.onlab.util.KryoNamespace; -import org.onosproject.cluster.ClusterDefinitionService; import org.onosproject.cluster.ClusterEvent; +import org.onosproject.cluster.ClusterMetadataService; import org.onosproject.cluster.ClusterStore; import org.onosproject.cluster.ClusterStoreDelegate; import org.onosproject.cluster.ControllerNode; @@ -99,14 +99,14 @@ public class DistributedClusterStore private ControllerNode localNode; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) - protected ClusterDefinitionService clusterDefinitionService; + protected ClusterMetadataService clusterMetadataService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected MessagingService messagingService; @Activate public void activate() { - localNode = clusterDefinitionService.localNode(); + localNode = clusterMetadataService.getLocalNode(); messagingService.registerHandler(HEARTBEAT_MESSAGE, new HeartbeatMessageHandler(), heartBeatMessageHandler); @@ -116,9 +116,6 @@ public class DistributedClusterStore heartBeatSender.scheduleWithFixedDelay(this::heartbeat, 0, HEARTBEAT_INTERVAL_MS, TimeUnit.MILLISECONDS); - addNode(localNode); - updateState(localNode.id(), State.ACTIVE); - log.info("Started"); } @@ -188,7 +185,7 @@ public class DistributedClusterStore private void addNode(ControllerNode node) { allNodes.put(node.id(), node); - updateState(node.id(), State.INACTIVE); + updateState(node.id(), node.equals(localNode) ? State.ACTIVE : State.INACTIVE); notifyDelegate(new ClusterEvent(ClusterEvent.Type.INSTANCE_ADDED, node)); } diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/StaticClusterMetadataStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/StaticClusterMetadataStore.java new file mode 100644 index 00000000..9f6c4130 --- /dev/null +++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/StaticClusterMetadataStore.java @@ -0,0 +1,221 @@ +package org.onosproject.store.cluster.impl; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.net.NetworkInterface.getNetworkInterfaces; +import static org.slf4j.LoggerFactory.getLogger; + +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; + +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.Service; +import org.onlab.packet.IpAddress; +import org.onosproject.cluster.ClusterMetadata; +import org.onosproject.cluster.ClusterMetadataEvent; +import org.onosproject.cluster.ClusterMetadataStore; +import org.onosproject.cluster.ClusterMetadataStoreDelegate; +import org.onosproject.cluster.ControllerNode; +import org.onosproject.cluster.DefaultControllerNode; +import org.onosproject.cluster.NodeId; +import org.onosproject.cluster.Partition; +import org.onosproject.store.AbstractStore; +import org.onosproject.store.service.Versioned; +import org.slf4j.Logger; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.google.common.base.Throwables; +import com.google.common.collect.Lists; +import com.google.common.io.Files; + +/** + * ClusterMetadataStore backed by a local file. + */ +@Component(immediate = true, enabled = true) +@Service +public class StaticClusterMetadataStore + extends AbstractStore<ClusterMetadataEvent, ClusterMetadataStoreDelegate> + implements ClusterMetadataStore { + + private final Logger log = getLogger(getClass()); + private static final String CLUSTER_METADATA_FILE = "../config/cluster.json"; + private static final int DEFAULT_ONOS_PORT = 9876; + private final File metadataFile = new File(CLUSTER_METADATA_FILE); + private AtomicReference<ClusterMetadata> metadata = new AtomicReference<>(); + private ObjectMapper mapper; + private long version; + + @Activate + public void activate() { + mapper = new ObjectMapper(); + SimpleModule module = new SimpleModule(); + module.addSerializer(NodeId.class, new NodeIdSerializer()); + module.addDeserializer(NodeId.class, new NodeIdDeserializer()); + module.addSerializer(ControllerNode.class, new ControllerNodeSerializer()); + module.addDeserializer(ControllerNode.class, new ControllerNodeDeserializer()); + mapper.registerModule(module); + File metadataFile = new File(CLUSTER_METADATA_FILE); + if (metadataFile.exists()) { + try { + metadata.set(mapper.readValue(metadataFile, ClusterMetadata.class)); + version = metadataFile.lastModified(); + } catch (IOException e) { + Throwables.propagate(e); + } + } else { + String localIp = getSiteLocalAddress(); + ControllerNode localNode = + new DefaultControllerNode(new NodeId(localIp), IpAddress.valueOf(localIp), DEFAULT_ONOS_PORT); + metadata.set(ClusterMetadata.builder() + .withName("default") + .withControllerNodes(Arrays.asList(localNode)) + .withPartitions(Lists.newArrayList(new Partition("p1", Lists.newArrayList(localNode.id())))) + .build()); + version = System.currentTimeMillis(); + } + log.info("Started"); + } + + @Deactivate + public void deactivate() { + log.info("Stopped"); + } + + @Override + public void setDelegate(ClusterMetadataStoreDelegate delegate) { + checkNotNull(delegate, "Delegate cannot be null"); + this.delegate = delegate; + } + + @Override + public void unsetDelegate(ClusterMetadataStoreDelegate delegate) { + this.delegate = null; + } + + @Override + public boolean hasDelegate() { + return this.delegate != null; + } + + @Override + public Versioned<ClusterMetadata> getClusterMetadata() { + return new Versioned<>(metadata.get(), version); + } + + @Override + public void setClusterMetadata(ClusterMetadata metadata) { + checkNotNull(metadata); + try { + Files.createParentDirs(metadataFile); + mapper.writeValue(metadataFile, metadata); + this.metadata.set(metadata); + } catch (IOException e) { + Throwables.propagate(e); + } + } + + @Override + public void setActiveReplica(String partitionId, NodeId nodeId) { + throw new UnsupportedOperationException(); + } + + @Override + public void unsetActiveReplica(String partitionId, NodeId nodeId) { + throw new UnsupportedOperationException(); + } + + @Override + public Collection<NodeId> getActiveReplicas(String partitionId) { + return metadata.get().getPartitions() + .stream() + .filter(r -> r.getName().equals(partitionId)) + .findFirst() + .map(r -> r.getMembers()) + .orElse(null); + } + + private static class ControllerNodeSerializer extends JsonSerializer<ControllerNode> { + @Override + public void serialize(ControllerNode node, JsonGenerator jgen, SerializerProvider provider) + throws IOException, JsonProcessingException { + jgen.writeStartObject(); + jgen.writeStringField("id", node.id().toString()); + jgen.writeStringField("ip", node.ip().toString()); + jgen.writeNumberField("port", node.tcpPort()); + jgen.writeEndObject(); + } + } + + private static class ControllerNodeDeserializer extends JsonDeserializer<ControllerNode> { + @Override + public ControllerNode deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + JsonNode node = jp.getCodec().readTree(jp); + NodeId nodeId = new NodeId(node.get("id").textValue()); + IpAddress ip = IpAddress.valueOf(node.get("ip").textValue()); + int port = node.get("port").asInt(); + return new DefaultControllerNode(nodeId, ip, port); + } + } + + private static class NodeIdSerializer extends JsonSerializer<NodeId> { + @Override + public void serialize(NodeId nodeId, JsonGenerator jgen, SerializerProvider provider) + throws IOException, JsonProcessingException { + jgen.writeString(nodeId.toString()); + } + } + + private class NodeIdDeserializer extends JsonDeserializer<NodeId> { + @Override + public NodeId deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + JsonNode node = jp.getCodec().readTree(jp); + return new NodeId(node.asText()); + } + } + + + private static String getSiteLocalAddress() { + Function<NetworkInterface, IpAddress> ipLookup = nif -> { + for (InetAddress address : Collections.list(nif.getInetAddresses())) { + if (address.isSiteLocalAddress()) { + return IpAddress.valueOf(address); + } + } + return null; + }; + try { + IpAddress ip = ipLookup.apply(NetworkInterface.getByName("eth0")); + if (ip != null) { + return ip.toString(); + } + for (NetworkInterface nif : Collections.list(getNetworkInterfaces())) { + ip = ipLookup.apply(nif); + if (ip != null) { + return ip.toString(); + } + } + } catch (Exception e) { + throw new IllegalStateException("Unable to get network interfaces", e); + } + return IpAddress.valueOf(InetAddress.getLoopbackAddress()).toString(); + } +}
\ No newline at end of file diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/IOLoopMessagingManager.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/IOLoopMessagingManager.java index ffdd25f2..ddb45f71 100644 --- a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/IOLoopMessagingManager.java +++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/IOLoopMessagingManager.java @@ -22,7 +22,7 @@ import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.ReferenceCardinality; import org.apache.felix.scr.annotations.Service; import org.onlab.nio.service.IOLoopMessaging; -import org.onosproject.cluster.ClusterDefinitionService; +import org.onosproject.cluster.ClusterMetadataService; import org.onosproject.cluster.ControllerNode; import org.onosproject.store.cluster.messaging.Endpoint; import org.slf4j.Logger; @@ -38,11 +38,11 @@ public class IOLoopMessagingManager extends IOLoopMessaging { private final Logger log = LoggerFactory.getLogger(getClass()); @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) - protected ClusterDefinitionService clusterDefinitionService; + protected ClusterMetadataService clusterMetadataService; @Activate public void activate() throws Exception { - ControllerNode localNode = clusterDefinitionService.localNode(); + ControllerNode localNode = clusterMetadataService.getLocalNode(); super.start(new Endpoint(localNode.ip(), localNode.tcpPort())); log.info("Started"); } diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/NettyMessagingManager.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/NettyMessagingManager.java index 9328817b..23c81869 100644 --- a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/NettyMessagingManager.java +++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/NettyMessagingManager.java @@ -16,6 +16,7 @@ package org.onosproject.store.cluster.messaging.impl; import com.google.common.base.Strings; + import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; @@ -23,7 +24,7 @@ import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.ReferenceCardinality; import org.apache.felix.scr.annotations.Service; import org.onlab.netty.NettyMessaging; -import org.onosproject.cluster.ClusterDefinitionService; +import org.onosproject.cluster.ClusterMetadataService; import org.onosproject.cluster.ControllerNode; import org.onosproject.store.cluster.messaging.Endpoint; import org.slf4j.Logger; @@ -41,11 +42,11 @@ public class NettyMessagingManager extends NettyMessaging { private static final short MIN_KS_LENGTH = 6; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) - protected ClusterDefinitionService clusterDefinitionService; + protected ClusterMetadataService clusterMetadataService; @Activate public void activate() throws Exception { - ControllerNode localNode = clusterDefinitionService.localNode(); + ControllerNode localNode = clusterMetadataService.getLocalNode(); getTLSParameters(); super.start(new Endpoint(localNode.ip(), localNode.tcpPort())); log.info("Started"); diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseDefinition.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseDefinition.java deleted file mode 100644 index 11b56c14..00000000 --- a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseDefinition.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * 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.store.consistent.impl; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Maps; -import org.onosproject.store.cluster.impl.NodeInfo; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * Partitioned database configuration. - */ -public class DatabaseDefinition { - private Map<String, Set<NodeInfo>> partitions; - private Set<NodeInfo> nodes; - - /** - * Creates a new DatabaseDefinition. - * - * @param partitions partition map - * @param nodes set of nodes - * @return database definition - */ - public static DatabaseDefinition from(Map<String, Set<NodeInfo>> partitions, - Set<NodeInfo> nodes) { - checkNotNull(partitions); - checkNotNull(nodes); - DatabaseDefinition definition = new DatabaseDefinition(); - definition.partitions = ImmutableMap.copyOf(partitions); - definition.nodes = ImmutableSet.copyOf(nodes); - return definition; - } - - /** - * Creates a new DatabaseDefinition using default partitions. - * - * @param nodes set of nodes - * @return database definition - */ - public static DatabaseDefinition from(Set<NodeInfo> nodes) { - return from(generateDefaultPartitions(nodes), nodes); - } - - /** - * Returns the map of database partitions. - * - * @return db partition map - */ - public Map<String, Set<NodeInfo>> getPartitions() { - return partitions; - } - - /** - * Returns the set of nodes. - * - * @return nodes - */ - public Set<NodeInfo> getNodes() { - return nodes; - } - - - /** - * Generates set of default partitions using permutations of the nodes. - * - * @param nodes information about cluster nodes - * @return default partition map - */ - private static Map<String, Set<NodeInfo>> generateDefaultPartitions(Set<NodeInfo> nodes) { - List<NodeInfo> sorted = new ArrayList<>(nodes); - Collections.sort(sorted, (o1, o2) -> o1.getId().compareTo(o2.getId())); - Map<String, Set<NodeInfo>> partitions = Maps.newHashMap(); - - int length = nodes.size(); - int count = 3; - for (int i = 0; i < length; i++) { - Set<NodeInfo> set = new HashSet<>(count); - for (int j = 0; j < count; j++) { - set.add(sorted.get((i + j) % length)); - } - partitions.put("p" + (i + 1), set); - } - return partitions; - } - -}
\ No newline at end of file diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseDefinitionStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseDefinitionStore.java deleted file mode 100644 index b77667b2..00000000 --- a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseDefinitionStore.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.store.consistent.impl; - -import static com.google.common.base.Preconditions.checkNotNull; -import java.io.File; -import java.io.IOException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.io.Files; - -/** - * Allows for reading and writing partitioned database definition as a JSON file. - */ -public class DatabaseDefinitionStore { - - private final File file; - - /** - * Creates a reader/writer of the database definition file. - * - * @param filePath location of the definition file - */ - public DatabaseDefinitionStore(String filePath) { - file = new File(checkNotNull(filePath)); - } - - /** - * Creates a reader/writer of the database definition file. - * - * @param filePath location of the definition file - */ - public DatabaseDefinitionStore(File filePath) { - file = checkNotNull(filePath); - } - - /** - * Returns the database definition. - * - * @return database definition - * @throws IOException when I/O exception of some sort has occurred. - */ - public DatabaseDefinition read() throws IOException { - ObjectMapper mapper = new ObjectMapper(); - return mapper.readValue(file, DatabaseDefinition.class); - } - - /** - * Writes the specified database definition to file. - * - * @param definition database definition - * @throws IOException when I/O exception of some sort has occurred. - */ - public void write(DatabaseDefinition definition) throws IOException { - checkNotNull(definition); - // write back to file - Files.createParentDirs(file); - ObjectMapper mapper = new ObjectMapper(); - mapper.writeValue(file, definition); - } -} diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseManager.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseManager.java index 6ea7c220..3e89635a 100644 --- a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseManager.java +++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseManager.java @@ -18,7 +18,6 @@ package org.onosproject.store.consistent.impl; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; @@ -50,12 +49,12 @@ import org.apache.felix.scr.annotations.Service; import org.onosproject.app.ApplicationEvent; import org.onosproject.app.ApplicationListener; import org.onosproject.app.ApplicationService; +import org.onosproject.cluster.ClusterMetadataService; import org.onosproject.cluster.ClusterService; +import org.onosproject.cluster.ControllerNode; import org.onosproject.cluster.NodeId; import org.onosproject.core.ApplicationId; import org.onosproject.core.IdGenerator; -import org.onosproject.store.cluster.impl.ClusterDefinitionManager; -import org.onosproject.store.cluster.impl.NodeInfo; import org.onosproject.store.cluster.messaging.ClusterCommunicationService; import org.onosproject.store.ecmap.EventuallyConsistentMapBuilderImpl; import org.onosproject.store.service.AtomicCounterBuilder; @@ -73,8 +72,6 @@ import org.onosproject.store.service.Transaction; import org.onosproject.store.service.TransactionContextBuilder; import org.slf4j.Logger; -import java.io.File; -import java.io.IOException; import java.util.Collection; import java.util.List; import java.util.Map; @@ -99,8 +96,6 @@ public class DatabaseManager implements StorageService, StorageAdminService { private final Logger log = getLogger(getClass()); - public static final int COPYCAT_TCP_PORT = 9876; - public static final String PARTITION_DEFINITION_FILE = "../config/tablets.json"; public static final String BASE_PARTITION_NAME = "p0"; private static final int RAFT_ELECTION_TIMEOUT_MILLIS = 3000; @@ -122,6 +117,9 @@ public class DatabaseManager implements StorageService, StorageAdminService { Multimaps.synchronizedMultimap(ArrayListMultimap.create()); @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ClusterMetadataService clusterMetadataService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected ClusterService clusterService; @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY, policy = ReferencePolicy.DYNAMIC) @@ -130,8 +128,9 @@ public class DatabaseManager implements StorageService, StorageAdminService { @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected ClusterCommunicationService clusterCommunicator; - protected String nodeToUri(NodeInfo node) { - return String.format("onos://%s:%d", node.getIp(), node.getTcpPort()); + protected String nodeIdToUri(NodeId nodeId) { + ControllerNode node = clusterService.getNode(nodeId); + return String.format("onos://%s:%d", node.ip(), node.tcpPort()); } protected void bindApplicationService(ApplicationService service) { @@ -147,30 +146,22 @@ public class DatabaseManager implements StorageService, StorageAdminService { @Activate public void activate() { localNodeId = clusterService.getLocalNode().id(); - // load database configuration - File databaseDefFile = new File(PARTITION_DEFINITION_FILE); - log.info("Loading database definition: {}", databaseDefFile.getAbsolutePath()); - Map<String, Set<NodeInfo>> partitionMap; - try { - DatabaseDefinitionStore databaseDefStore = new DatabaseDefinitionStore(databaseDefFile); - if (!databaseDefFile.exists()) { - createDefaultDatabaseDefinition(databaseDefStore); - } - partitionMap = databaseDefStore.read().getPartitions(); - } catch (IOException e) { - throw new IllegalStateException("Failed to load database config", e); - } + Map<String, Set<NodeId>> partitionMap = Maps.newHashMap(); + clusterMetadataService.getClusterMetadata().getPartitions().forEach(p -> { + partitionMap.put(p.getName(), Sets.newHashSet(p.getMembers())); + }); + String[] activeNodeUris = partitionMap.values() .stream() .reduce((s1, s2) -> Sets.union(s1, s2)) .get() .stream() - .map(this::nodeToUri) + .map(this::nodeIdToUri) .toArray(String[]::new); - String localNodeUri = nodeToUri(NodeInfo.of(clusterService.getLocalNode())); + String localNodeUri = nodeIdToUri(clusterMetadataService.getLocalNode().id()); Protocol protocol = new CopycatCommunicationProtocol(clusterService, clusterCommunicator); ClusterConfig clusterConfig = new ClusterConfig() @@ -198,7 +189,7 @@ public class DatabaseManager implements StorageService, StorageAdminService { List<Database> partitions = partitionMap.entrySet() .stream() .map(entry -> { - String[] replicas = entry.getValue().stream().map(this::nodeToUri).toArray(String[]::new); + String[] replicas = entry.getValue().stream().map(this::nodeIdToUri).toArray(String[]::new); return newDatabaseConfig(entry.getKey(), newPersistentLog(), replicas); }) .map(config -> { @@ -229,17 +220,6 @@ public class DatabaseManager implements StorageService, StorageAdminService { log.info("Started"); } - private void createDefaultDatabaseDefinition(DatabaseDefinitionStore store) { - // Assumes IPv4 is returned. - String ip = ClusterDefinitionManager.getSiteLocalAddress(); - NodeInfo node = NodeInfo.from(ip, ip, COPYCAT_TCP_PORT); - try { - store.write(DatabaseDefinition.from(ImmutableSet.of(node))); - } catch (IOException e) { - log.warn("Unable to write default cluster definition", e); - } - } - @Deactivate public void deactivate() { CompletableFuture.allOf(inMemoryDatabase.close(), partitionedDatabase.close()) diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultTransactionContext.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultTransactionContext.java index b66f424b..73888221 100644 --- a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultTransactionContext.java +++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DefaultTransactionContext.java @@ -86,7 +86,7 @@ public class DefaultTransactionContext implements TransactionContext { @SuppressWarnings("unchecked") @Override - public void commit() { + public boolean commit() { // TODO: rework commit implementation to be more intuitive checkState(isOpen, TX_NOT_OPEN_ERROR); CommitResponse response = null; @@ -95,10 +95,11 @@ public class DefaultTransactionContext implements TransactionContext { txMaps.values().forEach(m -> updates.addAll(m.prepareDatabaseUpdates())); Transaction transaction = new DefaultTransaction(transactionId, updates); response = Futures.getUnchecked(database.prepareAndCommit(transaction)); + return response.success(); + } catch (Exception e) { + abort(); + return false; } finally { - if (response != null && !response.success()) { - abort(); - } isOpen = false; } } diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/GossipDeviceStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/GossipDeviceStore.java index 973db494..687762e0 100644 --- a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/GossipDeviceStore.java +++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/device/impl/GossipDeviceStore.java @@ -427,6 +427,7 @@ public class GossipDeviceStore // Primary providers can respond to all changes, but ancillary ones // should respond only to annotation changes. + DeviceEvent event = null; if ((providerId.isAncillary() && annotationsChanged) || (!providerId.isAncillary() && (propertiesChanged || annotationsChanged))) { boolean replaced = devices.replace(newDevice.id(), oldDevice, newDevice); @@ -436,17 +437,18 @@ public class GossipDeviceStore providerId, oldDevice, devices.get(newDevice.id()) , newDevice); } - if (!providerId.isAncillary()) { - boolean wasOnline = availableDevices.contains(newDevice.id()); - markOnline(newDevice.id(), newTimestamp); - if (!wasOnline) { - notifyDelegateIfNotNull(new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, newDevice, null)); - } - } - return new DeviceEvent(DeviceEvent.Type.DEVICE_UPDATED, newDevice, null); + event = new DeviceEvent(DeviceEvent.Type.DEVICE_UPDATED, newDevice, null); } - return null; + + if (!providerId.isAncillary()) { + boolean wasOnline = availableDevices.contains(newDevice.id()); + markOnline(newDevice.id(), newTimestamp); + if (!wasOnline) { + notifyDelegateIfNotNull(new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, newDevice, null)); + } + } + return event; } @Override diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/MapDbPersistentStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/MapDbPersistentStore.java index e62a2d5c..f5ce47fc 100644 --- a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/MapDbPersistentStore.java +++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/ecmap/MapDbPersistentStore.java @@ -100,4 +100,4 @@ class MapDbPersistentStore<K, V> implements PersistentStore<K, V> { items.remove(keyBytes); database.commit(); } -} +}
\ No newline at end of file diff --git a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/newresource/impl/ConsistentResourceStore.java b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/newresource/impl/ConsistentResourceStore.java index 10f79eb0..687576c3 100644 --- a/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/newresource/impl/ConsistentResourceStore.java +++ b/framework/src/onos/core/store/dist/src/main/java/org/onosproject/store/newresource/impl/ConsistentResourceStore.java @@ -52,7 +52,7 @@ import static com.google.common.base.Preconditions.checkNotNull; /** * Implementation of ResourceStore using TransactionalMap. */ -@Component(immediate = true, enabled = false) +@Component(immediate = true) @Service @Beta public class ConsistentResourceStore implements ResourceStore { @@ -239,6 +239,18 @@ public class ConsistentResourceStore implements ResourceStore { } @Override + public Collection<ResourcePath> getChildResources(ResourcePath parent) { + checkNotNull(parent); + + Versioned<List<ResourcePath>> children = childMap.get(parent); + if (children == null) { + return Collections.emptyList(); + } + + return children.value(); + } + + @Override public <T> Collection<ResourcePath> getAllocatedResources(ResourcePath parent, Class<T> cls) { checkNotNull(parent); checkNotNull(cls); diff --git a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/intent/impl/PartitionManagerTest.java b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/intent/impl/PartitionManagerTest.java index 25e23d3a..61d1937e 100644 --- a/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/intent/impl/PartitionManagerTest.java +++ b/framework/src/onos/core/store/dist/src/test/java/org/onosproject/store/intent/impl/PartitionManagerTest.java @@ -325,5 +325,11 @@ public class PartitionManagerTest { return Objects.equals(this.hash(), that.hash()); } + + @Override + public int compareTo(Key o) { + Long thisHash = hash(); + return thisHash.compareTo(o.hash()); + } } } diff --git a/framework/src/onos/core/store/persistence/pom.xml b/framework/src/onos/core/store/persistence/pom.xml new file mode 100644 index 00000000..555de11a --- /dev/null +++ b/framework/src/onos/core/store/persistence/pom.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <repositories> + <repository> + <id>repository.springsource.com.release</id> + <name>SpringSource OBR - Release</name> + <url>http://repository.springsource.com/maven/bundles/release</url> + </repository> + <repository> + <id>repository.springsource.com.external</id> + <name>SpringSource OBR - External</name> + <url>http://repository.springsource.com/maven/bundles/external</url> + </repository> + </repositories> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-core-store</artifactId> + <version>1.4.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-core-persistence</artifactId> + <packaging>bundle</packaging> + + <description>ONOS Core persistent local store subsystem</description> + + + + <dependencies> + <!--<dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-api</artifactId> + </dependency>--> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-api</artifactId> + <classifier>tests</classifier> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mapdb</groupId> + <artifactId>mapdb</artifactId> + <version>1.0.8</version> + </dependency> + </dependencies> + +</project> diff --git a/framework/src/onos/core/store/persistence/src/main/java/org/onosproject/persistence/impl/DefaultPersistentMapBuilder.java b/framework/src/onos/core/store/persistence/src/main/java/org/onosproject/persistence/impl/DefaultPersistentMapBuilder.java new file mode 100644 index 00000000..88c7d148 --- /dev/null +++ b/framework/src/onos/core/store/persistence/src/main/java/org/onosproject/persistence/impl/DefaultPersistentMapBuilder.java @@ -0,0 +1,63 @@ +/* + * 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.persistence.impl; + +import org.mapdb.DB; +import org.onosproject.persistence.PersistentMapBuilder; +import org.onosproject.store.service.Serializer; + +import java.util.Map; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Default builder for persistent maps stored in the mapDB local database via the persistence service. + */ +public class DefaultPersistentMapBuilder<K, V> implements PersistentMapBuilder<K, V> { + + private final DB localDB; + + private String name = null; + + private Serializer serializer = null; + + + public DefaultPersistentMapBuilder(DB localDB) { + checkNotNull(localDB, "The local database cannot be null."); + this.localDB = localDB; + } + + public PersistentMapBuilder<K, V> withName(String name) { + this.name = PersistenceManager.MAP_PREFIX + checkNotNull(name); + return this; + } + + public PersistentMapBuilder<K, V> withSerializer(Serializer serializer) { + checkArgument(this.serializer == null); + checkNotNull(serializer); + this.serializer = serializer; + return this; + } + + public Map<K, V> build() { + checkNotNull(name, "The name must be assigned."); + checkNotNull(serializer, "The key serializer must be assigned."); + + return new PersistentMap<K, V>(serializer, localDB, name); + } +}
\ No newline at end of file diff --git a/framework/src/onos/core/store/persistence/src/main/java/org/onosproject/persistence/impl/DefaultPersistentSetBuilder.java b/framework/src/onos/core/store/persistence/src/main/java/org/onosproject/persistence/impl/DefaultPersistentSetBuilder.java new file mode 100644 index 00000000..e1544fb4 --- /dev/null +++ b/framework/src/onos/core/store/persistence/src/main/java/org/onosproject/persistence/impl/DefaultPersistentSetBuilder.java @@ -0,0 +1,59 @@ +/* + * 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.persistence.impl; + +import org.mapdb.DB; +import org.onosproject.persistence.PersistentSetBuilder; +import org.onosproject.store.service.Serializer; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Default builder for persistent sets stored in the mapDB local database via the persistence service.. + */ +public class DefaultPersistentSetBuilder<E> implements PersistentSetBuilder<E> { + + private final DB localDB; + + private String name = null; + + private Serializer serializer = null; + + public DefaultPersistentSetBuilder(DB localDB) { + this.localDB = checkNotNull(localDB, "The local database cannot be null."); + } + + public PersistentSetBuilder<E> withName(String name) { + this.name = PersistenceManager.SET_PREFIX + checkNotNull(name); + return this; + } + + public PersistentSetBuilder<E> withSerializer(Serializer serializer) { + checkArgument(this.serializer == null); + checkNotNull(serializer); + this.serializer = serializer; + return this; + } + + public PersistentSet<E> build() { + checkNotNull(name, "The name must be assigned."); + checkNotNull(serializer, "The serializer must be assigned."); + + return new PersistentSet<E>(serializer, localDB, name); + } +}
\ No newline at end of file diff --git a/framework/src/onos/core/store/persistence/src/main/java/org/onosproject/persistence/impl/PersistenceException.java b/framework/src/onos/core/store/persistence/src/main/java/org/onosproject/persistence/impl/PersistenceException.java new file mode 100644 index 00000000..f2ba20c2 --- /dev/null +++ b/framework/src/onos/core/store/persistence/src/main/java/org/onosproject/persistence/impl/PersistenceException.java @@ -0,0 +1,30 @@ +/* + * 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.persistence.impl; + +/** + * An exception defined for failures of the local persistent store system. + */ + +/** + * Throws an exception with the specified message. + */ +public class PersistenceException extends RuntimeException { + public PersistenceException(String s) { + super(s); + } +} diff --git a/framework/src/onos/core/store/persistence/src/main/java/org/onosproject/persistence/impl/PersistenceManager.java b/framework/src/onos/core/store/persistence/src/main/java/org/onosproject/persistence/impl/PersistenceManager.java new file mode 100644 index 00000000..64a8683a --- /dev/null +++ b/framework/src/onos/core/store/persistence/src/main/java/org/onosproject/persistence/impl/PersistenceManager.java @@ -0,0 +1,138 @@ +/* + * 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.persistence.impl; + +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.Service; +import org.mapdb.DB; +import org.mapdb.DBMaker; +import org.onosproject.persistence.PersistenceService; +import org.onosproject.persistence.PersistentMapBuilder; +import org.onosproject.persistence.PersistentSetBuilder; +import org.slf4j.Logger; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; + +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Service that maintains local disk backed maps and sets. This implementation automatically deletes empty structures + * on shutdown. + */ +@Component(immediate = true) +@Service +public class PersistenceManager implements PersistenceService { + + private static final String DATABASE_PATH = "../data/localDB"; + + private static final String ENCLOSING_FOLDER = "../data"; + + static final String MAP_PREFIX = "map:"; + + static final String SET_PREFIX = "set:"; + + private final Logger log = getLogger(getClass()); + + private DB localDB = null; + + private static final int FLUSH_FREQUENCY_MILLIS = 3000; + + private final Timer timer = new Timer(); + + private final CommitTask commitTask = new CommitTask(); + + @Activate + public void activate() { + Path dbPath = Paths.get(DATABASE_PATH); + Path dbFolderPath = Paths.get(ENCLOSING_FOLDER); + //Make sure the directory exists, if it does not, make it. + if (!dbFolderPath.toFile().isDirectory()) { + log.info("The specified folder location for the database did not exist and will be created."); + try { + Files.createDirectories(dbFolderPath); + } catch (IOException e) { + log.error("Could not create the required folder for the database."); + throw new PersistenceException("Database folder could not be created."); + } + } + //Notify if the database file does not exist. + boolean dbFound = Files.exists(dbPath); + if (!dbFound) { + log.info("The database file could not be located, a new database will be constructed."); + + } else { + log.info("A previous database file has been found."); + } + localDB = DBMaker.newFileDB(dbPath.toFile()) + .asyncWriteEnable() + .closeOnJvmShutdown() + .make(); + timer.schedule(commitTask, FLUSH_FREQUENCY_MILLIS, FLUSH_FREQUENCY_MILLIS); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + for (Map.Entry<String, Object> entry : localDB.getAll().entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + //This is a map implementation to be handled as such + if (value instanceof Map) { + Map asMap = (Map) value; + if (asMap.isEmpty()) { + //the map is empty and may be deleted + localDB.delete(key); + } + //This is a set implementation and can be handled as such + } else if (value instanceof Set) { + Set asSet = (Set) value; + if (asSet.isEmpty()) { + //the set is empty and may be deleted + localDB.delete(key); + } + } + } + localDB.commit(); + localDB.close(); + log.info("Stopped"); + } + + public <K, V> PersistentMapBuilder<K, V> persistentMapBuilder() { + return new DefaultPersistentMapBuilder<>(localDB); + } + + public <E> PersistentSetBuilder<E> persistentSetBuilder() { + return new DefaultPersistentSetBuilder<>(localDB); + } + + private class CommitTask extends TimerTask { + + @Override + public void run() { + localDB.commit(); + } + } +}
\ No newline at end of file diff --git a/framework/src/onos/core/store/persistence/src/main/java/org/onosproject/persistence/impl/PersistentMap.java b/framework/src/onos/core/store/persistence/src/main/java/org/onosproject/persistence/impl/PersistentMap.java new file mode 100644 index 00000000..4506bbda --- /dev/null +++ b/framework/src/onos/core/store/persistence/src/main/java/org/onosproject/persistence/impl/PersistentMap.java @@ -0,0 +1,192 @@ +/* + * 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.persistence.impl; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import org.mapdb.DB; +import org.mapdb.Hasher; +import org.onosproject.store.service.Serializer; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + + +/** + * A map implementation that stores and receives all data from a serialized internal map. + */ +public class PersistentMap<K, V> implements Map<K, V> { + + private final Serializer serializer; + + private final org.mapdb.DB database; + + private final Map<byte[], byte[]> items; + + private final String name; + + public PersistentMap(Serializer serializer, DB database, String name) { + this.serializer = checkNotNull(serializer); + this.database = checkNotNull(database); + this.name = checkNotNull(name); + + items = database + .createHashMap(name) + .keySerializer(org.mapdb.Serializer.BYTE_ARRAY) + .valueSerializer(org.mapdb.Serializer.BYTE_ARRAY) + .hasher(Hasher.BYTE_ARRAY) + .makeOrGet(); + } + + /** + * Reads this set in deserialized form into the provided map. + * + * @param items the map to be populated + */ + public void readInto(Map<K, V> items) { + this.items.forEach((keyBytes, valueBytes) -> + items.put(serializer.decode(keyBytes), + serializer.decode(valueBytes))); + } + + @Override + public V remove(Object key) { + checkNotNull(key, "Key can not be null."); + V removed = get(key); + items.remove(serializer.encode(key)); + return removed; + } + + @Override + public int size() { + return items.size(); + } + + @Override + public boolean isEmpty() { + return items.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + checkNotNull(key, "Key cannot be null."); + return items.containsKey(serializer.encode(key)); + } + + @Override + public boolean containsValue(Object value) { + checkNotNull(value, "Value cannot be null."); + byte[] serialized = serializer.encode(value); + for (byte[] compareValue : items.values()) { + boolean same = true; + if (compareValue == null) { + same = false; + } else if (compareValue.length != serialized.length) { + same = false; + } else { + for (int i = 0; i < serialized.length; i++) { + if (serialized[i] != compareValue[i]) { + same = false; + break; + } + } + } + if (same) { + return true; + } + } + return false; + } + + @Override + public V get(Object key) { + checkNotNull(key, "Key cannot be null."); + return serializer.decode(items.get(serializer.encode(key))); + } + + @Override + public V put(K key, V value) { + checkNotNull(key, "Key cannot be null."); + checkNotNull(value, "Value cannot be null."); + byte[] prevVal = items.put(serializer.encode(key), serializer.encode(value)); + if (prevVal == null) { + return null; + } + return serializer.decode(prevVal); + } + + @Override + public void putAll(Map<? extends K, ? extends V> m) { + checkNotNull(m, "The passed in map cannot be null."); + m.forEach((k, v) -> items.put(serializer.encode(k), serializer.encode(v))); + } + + @Override + public void clear() { + items.clear(); + } + + @Override + public Set<K> keySet() { + Set<K> keys = Sets.newHashSet(); + items.keySet().forEach(k -> keys.add(serializer.decode(k))); + return keys; + } + + @Override + public Collection<V> values() { + Collection<V> values = Sets.newHashSet(); + items.values().forEach(v -> values.add(serializer.decode(v))); + return values; + } + + @Override + public Set<Entry<K, V>> entrySet() { + Set<Entry<K, V>> entries = Sets.newHashSet(); + items.entrySet(). + forEach(e -> entries.add(Maps.immutableEntry(serializer.decode(e.getKey()), + serializer.decode(e.getValue())))); + return entries; + } + + @Override + public boolean equals(Object map) { + //This is not threadsafe and on larger maps incurs a significant processing cost + if (!(map instanceof Map)) { + return false; + } + Map asMap = (Map) map; + if (this.size() != asMap.size()) { + return false; + } + for (Entry entry : this.entrySet()) { + Object key = entry.getKey(); + if (!asMap.containsKey(key) || !asMap.get(key).equals(entry.getValue())) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + return super.hashCode(); + } +}
\ No newline at end of file diff --git a/framework/src/onos/core/store/persistence/src/main/java/org/onosproject/persistence/impl/PersistentSet.java b/framework/src/onos/core/store/persistence/src/main/java/org/onosproject/persistence/impl/PersistentSet.java new file mode 100644 index 00000000..26118cf6 --- /dev/null +++ b/framework/src/onos/core/store/persistence/src/main/java/org/onosproject/persistence/impl/PersistentSet.java @@ -0,0 +1,194 @@ +/* + * 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.persistence.impl; + +import com.google.common.collect.Iterators; +import org.mapdb.DB; +import org.mapdb.Hasher; +import org.mapdb.Serializer; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A set implementation that gets and receives all data from a serialized internal set. + */ +//TODO add locking for reads and writes +public class PersistentSet<E> implements Set<E> { + + private final org.onosproject.store.service.Serializer serializer; + + private final org.mapdb.DB database; + + private final Set<byte[]> items; + + private final String name; + + public PersistentSet(org.onosproject.store.service.Serializer serializer, DB database, String name) { + this.serializer = checkNotNull(serializer); + this.database = checkNotNull(database); + this.name = checkNotNull(name); + + items = database + .createHashSet(name) + .serializer(Serializer.BYTE_ARRAY) + .hasher(Hasher.BYTE_ARRAY) + .makeOrGet(); + } + + public void readInto(Set<E> items) { + this.items.forEach(item -> items.add(serializer.decode(item))); + } + + @Override + public int size() { + return items.size(); + } + + @Override + public boolean isEmpty() { + return items.isEmpty(); + } + + @Override + public boolean contains(Object o) { + checkNotNull(o, "The argument cannot be null"); + return items.contains(serializer.encode(o)); + } + + @Override + public Iterator<E> iterator() { + return Iterators.transform(items.iterator(), serializer::decode); + } + + @Override + public Object[] toArray() { + Object[] retArray = new Object[items.size()]; + int index = 0; + Iterator<byte[]> iterator = items.iterator(); + while (iterator.hasNext()) { + retArray[index] = serializer.decode(iterator.next()); + index++; + } + return retArray; + } + + @Override + public <T> T[] toArray(T[] a) { + checkNotNull(a, "The passed in array cannot be null."); + int index = 0; + Iterator<byte[]> iterator = items.iterator(); + T[] retArray; + if (a.length >= items.size()) { + retArray = a; + } else { + retArray = (T[]) new Object[items.size()]; + } + while (iterator.hasNext()) { + retArray[index++] = serializer.decode(iterator.next()); + } + if (retArray.length > items.size()) { + retArray[index] = null; + } + return retArray; + } + + @Override + public boolean add(E item) { + checkNotNull("Item to be added cannot be null."); + return items.add(serializer.encode(item)); + } + + @Override + public boolean remove(Object o) { + checkNotNull(o, "Item to be removed cannot be null."); + return items.remove(serializer.encode(o)); + } + + @Override + public boolean containsAll(Collection<?> c) { + checkNotNull(c, "Collection cannot be internal."); + for (Object item : c) { + if (!items.contains(serializer.encode(item))) { + return false; + } + } + return true; + } + + @Override + public boolean addAll(Collection<? extends E> c) { + checkNotNull(c, "The collection to be added cannot be null."); + boolean changed = false; + for (Object item : c) { + changed = items.add(serializer.encode(item)) || changed; + } + return changed; + } + + @Override + public boolean retainAll(Collection<?> c) { + boolean changed = false; + for (byte[] item : items) { + E deserialized = serializer.decode(item); + if (!c.contains(deserialized)) { + changed = items.remove(item) || changed; + } + } + return changed; + } + + @Override + public boolean removeAll(Collection<?> c) { + boolean changed = false; + for (Object item : c) { + changed = items.remove(serializer.encode(item)) || changed; + } + return changed; + } + + @Override + public void clear() { + items.clear(); + } + + @Override + public boolean equals(Object set) { + //This is not threadsafe and on larger sets incurs a significant processing cost + if (!(set instanceof Set)) { + return false; + } + Set asSet = (Set) set; + if (asSet.size() != this.size()) { + return false; + } + for (Object item : this) { + if (!asSet.contains(item)) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + return super.hashCode(); + } +}
\ No newline at end of file diff --git a/framework/src/onos/core/store/persistence/src/main/java/org/onosproject/persistence/impl/package-info.java b/framework/src/onos/core/store/persistence/src/main/java/org/onosproject/persistence/impl/package-info.java new file mode 100644 index 00000000..968a5046 --- /dev/null +++ b/framework/src/onos/core/store/persistence/src/main/java/org/onosproject/persistence/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Implementations of core persistence classes. + */ +package org.onosproject.persistence.impl; diff --git a/framework/src/onos/core/store/persistence/src/main/test/test/PersistentMapTest.java b/framework/src/onos/core/store/persistence/src/main/test/test/PersistentMapTest.java new file mode 100644 index 00000000..b059f18e --- /dev/null +++ b/framework/src/onos/core/store/persistence/src/main/test/test/PersistentMapTest.java @@ -0,0 +1,245 @@ +/* + * 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 test; + +import com.google.common.collect.Maps; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mapdb.DB; +import org.mapdb.DBMaker; +import org.onosproject.persistence.impl.PersistentMap; +import org.onosproject.store.service.Serializer; + +import java.nio.file.Paths; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Test suite for Persistent Map. + */ +public class PersistentMapTest { + + private Map<Integer, Integer> map = null; + private DB fakeDB = null; + + + /** + * Set up the database, create a map and a direct executor to handle it. + * + * @throws Exception if instantiation fails + */ + @Before + public void setUp() throws Exception { + //Creates a db, a map within it and a basic integer serializer (async writing is off) + fakeDB = DBMaker + .newFileDB(Paths.get("../testDb").toFile()) + .asyncWriteEnable() + .closeOnJvmShutdown() + .make(); + map = new PersistentMap<Integer, Integer>(new Serializer() { + @Override + public <T> byte[] encode(T object) { + if (object == null) { + return null; + } + int num = (Integer) object; + byte[] result = new byte[4]; + + result[0] = (byte) (num >> 24); + result[1] = (byte) (num >> 16); + result[2] = (byte) (num >> 8); + result[3] = (byte) num; + return result; + } + + @Override + public <T> T decode(byte[] bytes) { + if (bytes == null) { + return null; + } + int num = 0x00000000; + + num = num | bytes[0] << 24; + num = num | bytes[1] << 16; + num = num | bytes[2] << 8; + num = num | bytes[3]; + + return (T) new java.lang.Integer(num); + } + }, fakeDB, "map"); + } + + /** + * Clears and deletes the map, closes the datbase and deletes the file. + * + * @throws Exception if shutdown fails + */ + @After + public void tearDown() throws Exception { + map.clear(); + fakeDB.delete("map:map"); + fakeDB.commit(); + fakeDB.close(); + //This is key to prevent artifacts persisting between tests. + Paths.get("../testDB").toFile().delete(); + + + } + + @Test + public void testRemove() throws Exception { + //Checks removal and return values + fillMap(10); + assertEquals(10, map.size()); + for (int i = 0; i < 10; i++) { + assertEquals("The previous value was wrong.", new Integer(i), map.remove(i)); + assertNull("The previous value was wrong.", map.remove(i)); + //(i+1) compensates for base zero. + assertEquals("The size was wrong.", 10 - (i + 1), map.size()); + } + } + + @Test + public void testSize() throws Exception { + //Checks size values throughout addition and removal + for (int i = 0; i < 10; i++) { + map.put(i, i); + assertEquals("The map size is wrong.", i + 1, map.size()); + } + for (int i = 0; i < 10; i++) { + map.remove(i); + assertEquals("The map size is wrong.", 9 - i, map.size()); + } + } + + @Test + public void testIsEmpty() throws Exception { + //Checks empty condition + //asserts that the map starts out empty + assertTrue("Map should be empty", map.isEmpty()); + map.put(1, 1); + assertFalse("Map shouldn't be empty.", map.isEmpty()); + map.remove(1); + assertTrue("Map should be empty", map.isEmpty()); + } + + @Test + public void testContains() throws Exception { + //Checks both containsKey and containsValue be aware the implementations vary widely (value does not use mapDB + //due to object '=='being an insufficient check) + for (int i = 0; i < 10; i++) { + assertFalse("Map should not contain the key", map.containsKey(i)); + assertFalse("Map should not contain the value", map.containsValue(i)); + map.put(i, i); + assertTrue("Map should contain the key", map.containsKey(i)); + assertTrue("Map should contain the value", map.containsValue(i)); + } + } + + @Test + public void testGet() throws Exception { + //Tests value retrieval and nonexistent key return values + for (int i = 0; i < 10; i++) { + map.put(i, i); + for (int j = 0; j <= i; j++) { + assertEquals("The value was wrong.", new Integer(j), map.get(j)); + } + } + assertNull("Null return value for nonexistent keys.", map.get(10)); + } + + @Test + public void testPutAll() throws Exception { + //Tests adding of an outside map + Map<Integer, Integer> testMap = Maps.newHashMap(); + fillMap(10); + map.putAll(testMap); + for (int i = 0; i < 10; i++) { + assertTrue("The map should contain the current 'i' value.", map.containsKey(i)); + assertTrue("The map should contain the current 'i' value.", map.containsValue(i)); + } + } + + @Test + public void testClear() throws Exception { + //Tests clearing the map + assertTrue("Map was initialized incorrectly, should be empty.", map.isEmpty()); + fillMap(10); + assertFalse("Map should contain entries now.", map.isEmpty()); + map.clear(); + assertTrue("Map should have been cleared of entries.", map.isEmpty()); + + } + + @Test + public void testKeySet() throws Exception { + //Tests key set generation + fillMap(10); + Set<Integer> keys = map.keySet(); + for (int i = 0; i < 10; i++) { + assertTrue("The key set doesn't contain all keys 0-9", keys.contains(i)); + } + assertEquals("The key set has an incorrect number of entries", 10, keys.size()); + } + + @Test + public void testValues() throws Exception { + //Tests value set generation + fillMap(10); + Set<Integer> values = (Set<Integer>) map.values(); + for (int i = 0; i < 10; i++) { + assertTrue("The key set doesn't contain all keys 0-9", values.contains(i)); + } + assertEquals("The key set has an incorrect number of entries", 10, values.size()); + } + + @Test + public void testEntrySet() throws Exception { + //Test entry set generation (violates abstraction by knowing the type of the returned entries) + fillMap(10); + Set<Map.Entry<Integer, Integer>> entries = map.entrySet(); + for (int i = 0; i < 10; i++) { + assertTrue("The key set doesn't contain all keys 0-9", entries.contains(Maps.immutableEntry(i, i))); + } + assertEquals("The key set has an incorrect number of entries", 10, entries.size()); + } + + @Test public void testPut() throws Exception { + //Tests insertion behavior (particularly the returning of previous value) + fillMap(10); + for (int i = 0; i < 10; i++) { + assertEquals("Put should return the previous value", new Integer(i), map.put(i, i + 1)); + } + assertNull(map.put(11, 11)); + } + + /** + * Populated the map with pairs of integers from (0, 0) up to (numEntries - 1, numEntries -1). + * @param numEntries number of entries to add + */ + private void fillMap(int numEntries) { + for (int i = 0; i < numEntries; i++) { + map.put(i, i); + } + } +} diff --git a/framework/src/onos/core/store/persistence/src/main/test/test/PersistentSetTest.java b/framework/src/onos/core/store/persistence/src/main/test/test/PersistentSetTest.java new file mode 100644 index 00000000..3107ab30 --- /dev/null +++ b/framework/src/onos/core/store/persistence/src/main/test/test/PersistentSetTest.java @@ -0,0 +1,274 @@ +/* + * 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 test; + +import com.google.common.collect.Sets; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mapdb.DB; +import org.mapdb.DBMaker; +import org.onosproject.persistence.impl.PersistentSet; +import org.onosproject.store.service.Serializer; + +import java.nio.file.Paths; +import java.util.HashSet; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Test suite for Persistent Set. + */ +public class PersistentSetTest { + + private Set<Integer> set = null; + private DB fakeDB = null; + + @Before + public void setUp() throws Exception { + //Creates a db, a set within it and a basic integer serializer (async writing is off) + fakeDB = DBMaker + .newFileDB(Paths.get("../testDb").toFile()) + .asyncWriteEnable() + .closeOnJvmShutdown() + .make(); + set = new PersistentSet<Integer>(new Serializer() { + @Override + public <T> byte[] encode(T object) { + if (object == null) { + return null; + } + int num = (Integer) object; + byte[] result = new byte[4]; + + result[0] = (byte) (num >> 24); + result[1] = (byte) (num >> 16); + result[2] = (byte) (num >> 8); + result[3] = (byte) num; + return result; + } + + @Override + public <T> T decode(byte[] bytes) { + if (bytes == null) { + return null; + } + int num = 0x00000000; + + num = num | bytes[0] << 24; + num = num | bytes[1] << 16; + num = num | bytes[2] << 8; + num = num | bytes[3]; + + return (T) new java.lang.Integer(num); + } + }, fakeDB, "set"); + + } + + @After + public void tearDown() throws Exception { + set.clear(); + fakeDB.delete("map:map"); + fakeDB.commit(); + fakeDB.close(); + //This is key to prevent artifacts persisting between tests. + Paths.get("../testDB").toFile().delete(); + } + + @Test + public void testSize() throws Exception { + //Check correct sizing throughout population + for (int i = 0; i < 10; i++) { + set.add(i); + assertEquals("The set should have i + 1 entries.", i + 1, set.size()); + } + } + + @Test + public void testIsEmpty() throws Exception { + //test empty condition + assertTrue("The set should be initialized empty.", set.isEmpty()); + fillSet(5, this.set); + assertFalse("The set should no longer be empty.", set.isEmpty()); + set.clear(); + assertTrue("The set should have been cleared.", set.isEmpty()); + } + + @Test + public void testContains() throws Exception { + //Test contains + assertFalse("The set should not contain anything", set.contains(1)); + fillSet(10, this.set); + for (int i = 0; i < 10; i++) { + assertTrue("The set should contain all values 0-9.", set.contains(i)); + } + } + + @Test + public void testIterator() throws Exception { + //Test iterator behavior (no order guarantees are made) + Set<Integer> validationSet = Sets.newHashSet(); + fillSet(10, this.set); + fillSet(10, validationSet); + set.iterator().forEachRemaining(item -> assertTrue("Items were mismatched.", validationSet.remove(item))); + //All values should have been seen and removed + assertTrue("All entries in the validation set should have been removed.", validationSet.isEmpty()); + } + + @Test + public void testToArray() throws Exception { + //Test creation of a new array of the values + fillSet(10, set); + Object[] arr = set.toArray(); + assertEquals("The array should be of length 10.", 10, arr.length); + for (int i = 0; i < 10; i++) { + assertTrue("All elements of the array should be in the set.", set.contains((Integer) arr[i])); + } + } + + @Test + public void testToArray1() throws Exception { + //Test creation of a new array with the possibility of populating passed array if space allows + Integer[] originalArray = new Integer[9]; + fillSet(9, set); + //Test the case where the array and set match in size + Object[] retArray = set.toArray(originalArray); + assertEquals("If the set can fit the array should be the one passed in.", originalArray, retArray); + //Test the case where the passe array is too small to fit the set + set.add(9); + assertNotEquals("A new set should be generated if the contents will not fit in the passed set", + set.toArray(originalArray), originalArray); + //Now test the case where there should be a null terminator + set.clear(); + fillSet(5, set); + assertNull("The character one after last should be null if the array is larger than the set.", + set.toArray(originalArray)[5]); + } + + @Test + public void testAdd() throws Exception { + //Test of add + for (int i = 0; i < 10; i++) { + assertEquals("The size of the set is wrong.", i, set.size()); + assertTrue("The first add of an element should be true.", set.add(i)); + assertFalse("The second add of an element should be false.", set.add(i)); + } + } + + @Test + public void testRemove() throws Exception { + //Test removal + fillSet(10, set); + for (int i = 0; i < 10; i++) { + assertEquals("The size of the set is wrong.", 10 - i, set.size()); + assertTrue("The first removal should be true.", set.remove(i)); + assertFalse("The second removal should be false (item no longer contained).", set.remove(i)); + } + assertTrue("All elements should have been removed.", set.isEmpty()); + } + + @Test + public void testContainsAll() throws Exception { + //Test contains with short circuiting + Set<Integer> integersToCheck = Sets.newHashSet(); + fillSet(10, integersToCheck); + fillSet(10, set); + assertTrue("The sets should be identical so mutual subsets.", set.containsAll(integersToCheck)); + set.remove(9); + assertFalse("The set should contain one fewer value.", set.containsAll(integersToCheck)); + } + + @Test + public void testAddAll() throws Exception { + //Test multi-adds with change checking + Set<Integer> integersToCheck = Sets.newHashSet(); + fillSet(10, integersToCheck); + assertFalse("Set should be empty and so integers to check should not be a subset.", + set.containsAll(integersToCheck)); + assertTrue("The set should have changed as a result of add all.", set.addAll(integersToCheck)); + assertFalse("The set should not have changed as a result of add all a second time.", + set.addAll(integersToCheck)); + assertTrue("The sets should now be equivalent.", set.containsAll(integersToCheck)); + assertTrue("The sets should now be equivalent.", integersToCheck.containsAll(set)); + } + + @Test + public void testRetainAll() throws Exception { + //Test ability to generate the intersection set + Set<Integer> retainSet = Sets.newHashSet(); + fillSet(10, set); + assertTrue("The set should have changed.", set.retainAll(retainSet)); + assertTrue("The set should have been emptied.", set.isEmpty()); + fillSet(10, set); + fillSet(10, retainSet); + Set<Integer> duplicateSet = new HashSet<>(set); + assertFalse("The set should not have changed.", set.retainAll(retainSet)); + assertEquals("The set should be the same as the duplicate.", duplicateSet, set); + retainSet.remove(9); + assertTrue("The set should have changed.", set.retainAll(retainSet)); + duplicateSet.remove(9); + assertEquals("The set should have had the nine element removed.", duplicateSet, set); + } + + @Test + public void testRemoveAll() throws Exception { + //Test for mass removal and change checking + Set<Integer> removeSet = Sets.newHashSet(); + fillSet(10, set); + Set<Integer> duplicateSet = Sets.newHashSet(set); + assertFalse("No elements should change.", set.removeAll(removeSet)); + assertEquals("Set should not have diverged from the duplicate.", duplicateSet, set); + fillSet(5, removeSet); + assertTrue("Elements should have been removed.", set.removeAll(removeSet)); + assertNotEquals("Duplicate set should no longer be equivalent.", duplicateSet, set); + assertEquals("Five elements should have been removed from set.", 5, set.size()); + for (Integer item : removeSet) { + assertFalse("No element of remove set should remain.", set.contains(item)); + } + } + + @Test + public void testClear() throws Exception { + //Test set emptying + assertTrue("The set should be initialized empty.", set.isEmpty()); + set.clear(); + assertTrue("Clear should have no effect on an empty set.", set.isEmpty()); + fillSet(10, set); + assertFalse("The set should no longer be empty.", set.isEmpty()); + set.clear(); + assertTrue("The set should be empty after clear.", set.isEmpty()); + } + + /** + * Populated the map with integers from (0) up to (numEntries - 1). + * + * @param numEntries number of entries to add + */ + private void fillSet(int numEntries, Set<Integer> set) { + checkNotNull(set); + for (int i = 0; i < numEntries; i++) { + set.add(i); + } + } +} diff --git a/framework/src/onos/core/store/pom.xml b/framework/src/onos/core/store/pom.xml index 2b246b83..219ae5d0 100644 --- a/framework/src/onos/core/store/pom.xml +++ b/framework/src/onos/core/store/pom.xml @@ -33,7 +33,8 @@ <modules> <module>dist</module> - <module>serializers</module> + <module>persistence</module> + <module>serializers</module> </modules> <dependencies> diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ExtensionInstructionSerializer.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ExtensionInstructionSerializer.java new file mode 100644 index 00000000..6b12df96 --- /dev/null +++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/ExtensionInstructionSerializer.java @@ -0,0 +1,73 @@ +/* + * 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.store.serializers; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.Serializer; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import org.onlab.osgi.DefaultServiceDirectory; +import org.onosproject.net.DeviceId; +import org.onosproject.net.behaviour.ExtensionResolver; +import org.onosproject.net.driver.DefaultDriverData; +import org.onosproject.net.driver.DefaultDriverHandler; +import org.onosproject.net.driver.DriverHandler; +import org.onosproject.net.driver.DriverService; +import org.onosproject.net.flow.instructions.ExtensionInstruction; +import org.onosproject.net.flow.instructions.ExtensionType; +import org.onosproject.net.flow.instructions.Instructions; + +/** + * Created by jono on 10/29/15. + */ +public class ExtensionInstructionSerializer extends + Serializer<Instructions.ExtensionInstructionWrapper> { + + public ExtensionInstructionSerializer() { + super(false, true); + } + + @Override + public void write(Kryo kryo, Output output, Instructions.ExtensionInstructionWrapper object) { + kryo.writeClassAndObject(output, object.extensionInstruction().type()); + kryo.writeClassAndObject(output, object.deviceId()); + + kryo.writeClassAndObject(output, object.extensionInstruction().serialize()); + + } + + @Override + public Instructions.ExtensionInstructionWrapper read(Kryo kryo, Input input, + Class<Instructions.ExtensionInstructionWrapper> type) { + ExtensionType exType = (ExtensionType) kryo.readClassAndObject(input); + DeviceId deviceId = (DeviceId) kryo.readClassAndObject(input); + + DriverService driverService = DefaultServiceDirectory.getService(DriverService.class); + DriverHandler handler = new DefaultDriverHandler( + new DefaultDriverData(driverService.getDriver(deviceId), deviceId)); + + ExtensionResolver resolver = handler.behaviour(ExtensionResolver.class); + + ExtensionInstruction instruction = resolver.getExtensionInstruction(exType); + + byte[] bytes = (byte[]) kryo.readClassAndObject(input); + + instruction.deserialize(bytes); + + return Instructions.extension(instruction, deviceId); + } +} diff --git a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java index 5b5056cb..0312bafd 100644 --- a/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java +++ b/framework/src/onos/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java @@ -70,6 +70,7 @@ import org.onosproject.net.OchPort; import org.onosproject.net.OchSignal; import org.onosproject.net.OchSignalType; import org.onosproject.net.OduCltPort; +import org.onosproject.net.OduSignalId; import org.onosproject.net.OduSignalType; import org.onosproject.net.OmsPort; import org.onosproject.net.Port; @@ -118,6 +119,8 @@ import org.onosproject.net.flow.criteria.MetadataCriterion; import org.onosproject.net.flow.criteria.MplsCriterion; import org.onosproject.net.flow.criteria.OchSignalCriterion; import org.onosproject.net.flow.criteria.OchSignalTypeCriterion; +import org.onosproject.net.flow.criteria.OduSignalIdCriterion; +import org.onosproject.net.flow.criteria.OduSignalTypeCriterion; import org.onosproject.net.flow.criteria.PortCriterion; import org.onosproject.net.flow.criteria.SctpPortCriterion; import org.onosproject.net.flow.criteria.TcpPortCriterion; @@ -125,8 +128,10 @@ import org.onosproject.net.flow.criteria.TunnelIdCriterion; import org.onosproject.net.flow.criteria.UdpPortCriterion; import org.onosproject.net.flow.criteria.VlanIdCriterion; import org.onosproject.net.flow.criteria.VlanPcpCriterion; +import org.onosproject.net.flow.instructions.ExtensionType; import org.onosproject.net.flow.instructions.Instructions; import org.onosproject.net.flow.instructions.L0ModificationInstruction; +import org.onosproject.net.flow.instructions.L1ModificationInstruction; import org.onosproject.net.flow.instructions.L2ModificationInstruction; import org.onosproject.net.flow.instructions.L3ModificationInstruction; import org.onosproject.net.flow.instructions.L4ModificationInstruction; @@ -337,6 +342,8 @@ public final class KryoNamespaces { IndexedLambdaCriterion.class, OchSignalCriterion.class, OchSignalTypeCriterion.class, + OduSignalIdCriterion.class, + OduSignalTypeCriterion.class, Criterion.class, Criterion.Type.class, DefaultTrafficTreatment.class, @@ -349,6 +356,9 @@ public final class KryoNamespaces { L0ModificationInstruction.L0SubType.class, L0ModificationInstruction.ModLambdaInstruction.class, L0ModificationInstruction.ModOchSignalInstruction.class, + L1ModificationInstruction.class, + L1ModificationInstruction.L1SubType.class, + L1ModificationInstruction.ModOduSignalIdInstruction.class, L2ModificationInstruction.class, L2ModificationInstruction.L2SubType.class, L2ModificationInstruction.ModEtherInstruction.class, @@ -441,6 +451,8 @@ public final class KryoNamespaces { .register(new HostLocationSerializer(), HostLocation.class) .register(new DefaultOutboundPacketSerializer(), DefaultOutboundPacket.class) .register(new AnnotationsSerializer(), DefaultAnnotations.class) + .register(new ExtensionInstructionSerializer(), Instructions.ExtensionInstructionWrapper.class) + .register(ExtensionType.class) .register(Versioned.class) .register(MapEvent.class) .register(MapEvent.Type.class) @@ -458,6 +470,7 @@ public final class KryoNamespaces { .register(OduCltPort.SignalType.class) .register(IndexedLambda.class) .register(OchSignal.class) + .register(OduSignalId.class) .register(OduCltPortDescription.class) .register(OchPortDescription.class) .register(OmsPortDescription.class) |