Initial support for multi kubernetes clusters for k8s nodes
Change-Id: I6ca132898f8e157e0583de38a637fdc135f21d6f
(cherry picked from commit e2a04cedde73618ef24575e70cb221e03854de1d)
diff --git a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/cli/K8sNodeCheckCommand.java b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/cli/K8sNodeCheckCommand.java
index f421de0..2060712 100644
--- a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/cli/K8sNodeCheckCommand.java
+++ b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/cli/K8sNodeCheckCommand.java
@@ -27,16 +27,6 @@
import org.onosproject.net.Port;
import org.onosproject.net.device.DeviceService;
-import static org.onosproject.k8snode.api.Constants.EXTERNAL_BRIDGE;
-import static org.onosproject.k8snode.api.Constants.GENEVE_TUNNEL;
-import static org.onosproject.k8snode.api.Constants.GRE_TUNNEL;
-import static org.onosproject.k8snode.api.Constants.INTEGRATION_BRIDGE;
-import static org.onosproject.k8snode.api.Constants.INTEGRATION_TO_EXTERNAL_BRIDGE;
-import static org.onosproject.k8snode.api.Constants.INTEGRATION_TO_LOCAL_BRIDGE;
-import static org.onosproject.k8snode.api.Constants.LOCAL_BRIDGE;
-import static org.onosproject.k8snode.api.Constants.LOCAL_TO_INTEGRATION_BRIDGE;
-import static org.onosproject.k8snode.api.Constants.PHYSICAL_EXTERNAL_BRIDGE;
-import static org.onosproject.k8snode.api.Constants.VXLAN_TUNNEL;
import static org.onosproject.net.AnnotationKeys.PORT_NAME;
/**
@@ -71,22 +61,22 @@
if (intgBridge != null) {
print("%s %s=%s available=%s %s",
deviceService.isAvailable(intgBridge.id()) ? MSG_OK : MSG_ERROR,
- INTEGRATION_BRIDGE,
+ node.intgBridgeName(),
intgBridge.id(),
deviceService.isAvailable(intgBridge.id()),
intgBridge.annotations());
- printPortState(deviceService, node.intgBridge(), INTEGRATION_BRIDGE);
- printPortState(deviceService, node.intgBridge(), INTEGRATION_TO_EXTERNAL_BRIDGE);
- printPortState(deviceService, node.intgBridge(), INTEGRATION_TO_LOCAL_BRIDGE);
+ printPortState(deviceService, node.intgBridge(), node.intgBridgePortName());
+ printPortState(deviceService, node.intgBridge(), node.intgToExtPatchPortName());
+ printPortState(deviceService, node.intgBridge(), node.intgToLocalPatchPortName());
if (node.dataIp() != null) {
- printPortState(deviceService, node.intgBridge(), VXLAN_TUNNEL);
- printPortState(deviceService, node.intgBridge(), GRE_TUNNEL);
- printPortState(deviceService, node.intgBridge(), GENEVE_TUNNEL);
+ printPortState(deviceService, node.intgBridge(), node.vxlanPortName());
+ printPortState(deviceService, node.intgBridge(), node.grePortName());
+ printPortState(deviceService, node.intgBridge(), node.genevePortName());
}
} else {
print("%s %s=%s is not available",
MSG_ERROR,
- INTEGRATION_BRIDGE,
+ node.intgBridgeName(),
node.intgBridge());
}
@@ -95,16 +85,16 @@
if (extBridge != null) {
print("%s %s=%s available=%s %s",
deviceService.isAvailable(extBridge.id()) ? MSG_OK : MSG_ERROR,
- EXTERNAL_BRIDGE,
+ node.extBridgeName(),
extBridge.id(),
deviceService.isAvailable(extBridge.id()),
extBridge.annotations());
- printPortState(deviceService, node.extBridge(), EXTERNAL_BRIDGE);
- printPortState(deviceService, node.extBridge(), PHYSICAL_EXTERNAL_BRIDGE);
+ printPortState(deviceService, node.extBridge(), node.extBridgePortName());
+ printPortState(deviceService, node.extBridge(), node.extToIntgPatchPortName());
} else {
print("%s %s=%s is not available",
MSG_ERROR,
- EXTERNAL_BRIDGE,
+ node.extBridgeName(),
node.extBridge());
}
@@ -113,12 +103,12 @@
if (localBridge != null) {
print("%s %s=%s available=%s %s",
deviceService.isAvailable(localBridge.id()) ? MSG_OK : MSG_ERROR,
- LOCAL_BRIDGE,
+ node.localBridgeName(),
localBridge.id(),
deviceService.isAvailable(localBridge.id()),
localBridge.annotations());
- printPortState(deviceService, node.localBridge(), LOCAL_BRIDGE);
- printPortState(deviceService, node.localBridge(), LOCAL_TO_INTEGRATION_BRIDGE);
+ printPortState(deviceService, node.localBridge(), node.localBridgePortName());
+ printPortState(deviceService, node.localBridge(), node.localToIntgPatchPortName());
}
}
diff --git a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/codec/HostNodesInfoCodec.java b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/codec/HostNodesInfoCodec.java
new file mode 100644
index 0000000..4287418
--- /dev/null
+++ b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/codec/HostNodesInfoCodec.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2020-present Open Networking Foundation
+ *
+ * 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.k8snode.codec;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.packet.IpAddress;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.k8snode.api.DefaultHostNodesInfo;
+import org.onosproject.k8snode.api.HostNodesInfo;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.onlab.util.Tools.nullIsIllegal;
+
+/**
+ * HostNodesInfo codec used for serializing and de-serializing JSON.
+ */
+public final class HostNodesInfoCodec extends JsonCodec<HostNodesInfo> {
+
+ private static final String HOST_IP = "hostIp";
+ private static final String NODES = "nodes";
+
+ private static final String MISSING_MESSAGE = " is required in HostNodesInfo";
+
+ @Override
+ public ObjectNode encode(HostNodesInfo entity, CodecContext context) {
+ ObjectNode node = context.mapper().createObjectNode()
+ .put(HOST_IP, entity.hostIp().toString());
+
+ ArrayNode nodes = context.mapper().createArrayNode();
+ entity.nodes().forEach(nodes::add);
+ node.set(NODES, nodes);
+ return node;
+ }
+
+ @Override
+ public HostNodesInfo decode(ObjectNode json, CodecContext context) {
+ if (json == null || !json.isObject()) {
+ return null;
+ }
+
+ IpAddress hostIp = IpAddress.valueOf(nullIsIllegal(
+ json.get(HOST_IP).asText(), HOST_IP + MISSING_MESSAGE));
+
+ Set<String> nodes = new HashSet<>();
+ ArrayNode nodesJson = (ArrayNode) json.get(NODES);
+
+ for (JsonNode cidrJson : nodesJson) {
+ nodes.add(cidrJson.asText());
+ }
+
+ return new DefaultHostNodesInfo.Builder()
+ .hostIp(hostIp)
+ .nodes(nodes)
+ .build();
+ }
+}
diff --git a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/codec/K8sApiConfigCodec.java b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/codec/K8sApiConfigCodec.java
index b7da2a0..5b8c05e 100644
--- a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/codec/K8sApiConfigCodec.java
+++ b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/codec/K8sApiConfigCodec.java
@@ -16,16 +16,26 @@
package org.onosproject.k8snode.codec;
import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.commons.lang.StringUtils;
import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
import org.onosproject.codec.CodecContext;
import org.onosproject.codec.JsonCodec;
import org.onosproject.k8snode.api.DefaultK8sApiConfig;
+import org.onosproject.k8snode.api.HostNodesInfo;
import org.onosproject.k8snode.api.K8sApiConfig;
+import org.onosproject.k8snode.api.K8sApiConfig.Mode;
import org.onosproject.k8snode.api.K8sApiConfig.Scheme;
+import java.util.HashSet;
+import java.util.Set;
+
import static org.onlab.util.Tools.nullIsIllegal;
+import static org.onosproject.k8snode.api.Constants.DEFAULT_CLUSTER_NAME;
+import static org.onosproject.k8snode.api.Constants.DEFAULT_CONFIG_MODE;
+import static org.onosproject.k8snode.api.Constants.DEFAULT_SEGMENT_ID;
import static org.onosproject.k8snode.api.K8sApiConfig.Scheme.HTTPS;
import static org.onosproject.k8snode.api.K8sApiConfig.State.DISCONNECTED;
@@ -34,7 +44,11 @@
*/
public final class K8sApiConfigCodec extends JsonCodec<K8sApiConfig> {
+ private static final String CLUSTER_NAME = "clusterName";
+ private static final String SEGMENT_ID = "segmentId";
+ private static final String EXT_NETWORK_CIDR = "extNetworkCidr";
private static final String SCHEME = "scheme";
+ private static final String MODE = "mode";
private static final String IP_ADDRESS = "ipAddress";
private static final String PORT = "port";
private static final String STATE = "state";
@@ -42,12 +56,16 @@
private static final String CA_CERT_DATA = "caCertData";
private static final String CLIENT_CERT_DATA = "clientCertData";
private static final String CLIENT_KEY_DATA = "clientKeyData";
+ private static final String HOST_NODES_INFO = "hostNodesInfo";
private static final String MISSING_MESSAGE = " is required in K8sApiConfig";
@Override
public ObjectNode encode(K8sApiConfig entity, CodecContext context) {
ObjectNode node = context.mapper().createObjectNode()
+ .put(CLUSTER_NAME, entity.clusterName())
+ .put(SEGMENT_ID, entity.segmentId())
+ .put(MODE, entity.mode().name())
.put(SCHEME, entity.scheme().name())
.put(IP_ADDRESS, entity.ipAddress().toString())
.put(PORT, entity.port())
@@ -80,6 +98,17 @@
}
}
+ if (entity.extNetworkCidr() != null) {
+ node.put(EXT_NETWORK_CIDR, entity.extNetworkCidr().toString());
+ }
+
+ ArrayNode infos = context.mapper().createArrayNode();
+ entity.infos().forEach(info -> {
+ ObjectNode infoJson = context.codec(HostNodesInfo.class).encode(info, context);
+ infos.add(infoJson);
+ });
+ node.set(HOST_NODES_INFO, infos);
+
return node;
}
@@ -89,6 +118,32 @@
return null;
}
+ JsonNode clusterNameJson = json.get(CLUSTER_NAME);
+ String clusterNameStr = "";
+
+ if (clusterNameJson == null) {
+ clusterNameStr = DEFAULT_CLUSTER_NAME;
+ } else {
+ clusterNameStr = clusterNameJson.asText();
+ }
+
+ JsonNode segmentIdJson = json.get(SEGMENT_ID);
+ int segmentId = DEFAULT_SEGMENT_ID;
+
+ if (segmentIdJson != null) {
+ segmentId = segmentIdJson.asInt();
+ }
+
+ JsonNode modeJson = json.get(MODE);
+ String modeStr = "";
+ if (modeJson == null) {
+ modeStr = DEFAULT_CONFIG_MODE;
+ } else {
+ modeStr = modeJson.asText();
+ }
+
+ Mode mode = Mode.valueOf(modeStr);
+
Scheme scheme = Scheme.valueOf(nullIsIllegal(
json.get(SCHEME).asText(), SCHEME + MISSING_MESSAGE));
IpAddress ipAddress = IpAddress.valueOf(nullIsIllegal(
@@ -96,6 +151,9 @@
int port = json.get(PORT).asInt();
K8sApiConfig.Builder builder = DefaultK8sApiConfig.builder()
+ .clusterName(clusterNameStr)
+ .segmentId(segmentId)
+ .mode(mode)
.scheme(scheme)
.ipAddress(ipAddress)
.port(port)
@@ -157,6 +215,22 @@
builder.clientKeyData(clientKeyData);
}
+ JsonNode extNetworkCidrJson = json.get(EXT_NETWORK_CIDR);
+ if (extNetworkCidrJson != null) {
+ builder.extNetworkCidr(IpPrefix.valueOf(extNetworkCidrJson.asText()));
+ }
+
+ Set<HostNodesInfo> infos = new HashSet<>();
+ ArrayNode infosJson = (ArrayNode) json.get(HOST_NODES_INFO);
+ if (infosJson != null) {
+ for (JsonNode infoJson : infosJson) {
+ HostNodesInfo info = context.codec(HostNodesInfo.class)
+ .decode((ObjectNode) infoJson, context);
+ infos.add(info);
+ }
+ builder.infos(infos);
+ }
+
return builder.build();
}
}
diff --git a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/codec/K8sHostCodec.java b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/codec/K8sHostCodec.java
new file mode 100644
index 0000000..2edb66d
--- /dev/null
+++ b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/codec/K8sHostCodec.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2020-present Open Networking Foundation
+ *
+ * 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.k8snode.codec;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.packet.IpAddress;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.k8snode.api.DefaultK8sHost;
+import org.onosproject.k8snode.api.K8sHost;
+import org.onosproject.k8snode.api.K8sHostState;
+import org.slf4j.Logger;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Kubernetes host codec used for serializing and de-serializing JSON string.
+ */
+public final class K8sHostCodec extends JsonCodec<K8sHost> {
+
+ private final Logger log = getLogger(getClass());
+
+ private static final String HOST_IP = "hostIp";
+ private static final String NODE_NAMES = "nodeNames";
+ private static final String STATE = "state";
+
+ private static final String MISSING_MESSAGE = " is required in K8sHost";
+
+ @Override
+ public ObjectNode encode(K8sHost entity, CodecContext context) {
+ checkNotNull(entity, "Kubernetes host cannot be null");
+
+ ObjectNode result = context.mapper().createObjectNode()
+ .put(HOST_IP, entity.hostIp().toString())
+ .put(STATE, entity.state().name());
+
+ ArrayNode nodes = context.mapper().createArrayNode();
+ entity.nodeNames().forEach(nodes::add);
+ result.set(NODE_NAMES, nodes);
+
+ return result;
+ }
+
+ @Override
+ public K8sHost decode(ObjectNode json, CodecContext context) {
+ if (json == null || !json.isObject()) {
+ return null;
+ }
+
+ IpAddress hostIp = IpAddress.valueOf(nullIsIllegal(json.get(HOST_IP).asText(),
+ HOST_IP + MISSING_MESSAGE));
+ ArrayNode nodeNamesJson = (ArrayNode) json.get(NODE_NAMES);
+ Set<String> nodeNames = new HashSet<>();
+ nodeNamesJson.forEach(n -> nodeNames.add(n.asText()));
+
+ return DefaultK8sHost.builder()
+ .hostIp(hostIp)
+ .state(K8sHostState.INIT)
+ .nodeNames(nodeNames)
+ .build();
+ }
+}
diff --git a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/codec/K8sNodeCodec.java b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/codec/K8sNodeCodec.java
index 41bad16..8793c46 100644
--- a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/codec/K8sNodeCodec.java
+++ b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/codec/K8sNodeCodec.java
@@ -17,6 +17,7 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.commons.lang.StringUtils;
import org.onlab.packet.IpAddress;
import org.onosproject.codec.CodecContext;
import org.onosproject.codec.JsonCodec;
@@ -28,6 +29,8 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onlab.util.Tools.nullIsIllegal;
+import static org.onosproject.k8snode.api.Constants.DEFAULT_CLUSTER_NAME;
+import static org.onosproject.k8snode.api.Constants.DEFAULT_SEGMENT_ID;
import static org.slf4j.LoggerFactory.getLogger;
/**
@@ -37,13 +40,16 @@
private final Logger log = getLogger(getClass());
+ private static final String CLUSTER_NAME = "clusterName";
private static final String HOSTNAME = "hostname";
private static final String TYPE = "type";
+ private static final String SEGMENT_ID = "segmentId";
private static final String MANAGEMENT_IP = "managementIp";
private static final String DATA_IP = "dataIp";
private static final String INTEGRATION_BRIDGE = "integrationBridge";
private static final String EXTERNAL_BRIDGE = "externalBridge";
private static final String LOCAL_BRIDGE = "localBridge";
+ private static final String TUNNEL_BRIDGE = "tunnelBridge";
private static final String STATE = "state";
private static final String EXTERNAL_INTF = "externalInterface";
private static final String EXTERNAL_BRIDGE_IP = "externalBridgeIp";
@@ -56,8 +62,10 @@
checkNotNull(node, "Kubernetes node cannot be null");
ObjectNode result = context.mapper().createObjectNode()
+ .put(CLUSTER_NAME, node.clusterName())
.put(HOSTNAME, node.hostname())
.put(TYPE, node.type().name())
+ .put(SEGMENT_ID, node.segmentId())
.put(STATE, node.state().name())
.put(MANAGEMENT_IP, node.managementIp().toString());
@@ -73,6 +81,10 @@
result.put(LOCAL_BRIDGE, node.localBridge().toString());
}
+ if (node.tunBridge() != null) {
+ result.put(TUNNEL_BRIDGE, node.tunBridge().toString());
+ }
+
if (node.dataIp() != null) {
result.put(DATA_IP, node.dataIp().toString());
}
@@ -98,6 +110,12 @@
return null;
}
+ String clusterName = json.get(CLUSTER_NAME).asText();
+
+ if (StringUtils.isEmpty(clusterName)) {
+ clusterName = DEFAULT_CLUSTER_NAME;
+ }
+
String hostname = nullIsIllegal(json.get(HOSTNAME).asText(),
HOSTNAME + MISSING_MESSAGE);
String type = nullIsIllegal(json.get(TYPE).asText(),
@@ -106,6 +124,7 @@
MANAGEMENT_IP + MISSING_MESSAGE);
DefaultK8sNode.Builder nodeBuilder = DefaultK8sNode.builder()
+ .clusterName(clusterName)
.hostname(hostname)
.type(K8sNode.Type.valueOf(type))
.state(K8sNodeState.INIT)
@@ -115,6 +134,13 @@
nodeBuilder.dataIp(IpAddress.valueOf(json.get(DATA_IP).asText()));
}
+ JsonNode segmentIdJson = json.get(SEGMENT_ID);
+ int segmentId = DEFAULT_SEGMENT_ID;
+ if (segmentIdJson != null) {
+ segmentId = segmentIdJson.asInt();
+ }
+ nodeBuilder.segmentId(segmentId);
+
JsonNode intBridgeJson = json.get(INTEGRATION_BRIDGE);
if (intBridgeJson != null) {
nodeBuilder.intgBridge(DeviceId.deviceId(intBridgeJson.asText()));
@@ -130,6 +156,11 @@
nodeBuilder.localBridge(DeviceId.deviceId(localBridgeJson.asText()));
}
+ JsonNode tunBridgeJson = json.get(TUNNEL_BRIDGE);
+ if (tunBridgeJson != null) {
+ nodeBuilder.tunBridge(DeviceId.deviceId(tunBridgeJson.asText()));
+ }
+
JsonNode extIntfJson = json.get(EXTERNAL_INTF);
if (extIntfJson != null) {
nodeBuilder.extIntf(extIntfJson.asText());
diff --git a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DefaultK8sApiConfigHandler.java b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DefaultK8sApiConfigHandler.java
index 01a5043..33415a8 100644
--- a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DefaultK8sApiConfigHandler.java
+++ b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DefaultK8sApiConfigHandler.java
@@ -25,6 +25,8 @@
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.k8snode.api.DefaultK8sNode;
+import org.onosproject.k8snode.api.ExternalNetworkService;
+import org.onosproject.k8snode.api.HostNodesInfo;
import org.onosproject.k8snode.api.K8sApiConfig;
import org.onosproject.k8snode.api.K8sApiConfigAdminService;
import org.onosproject.k8snode.api.K8sApiConfigEvent;
@@ -44,6 +46,9 @@
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.k8snode.api.Constants.DEFAULT_CLUSTER_NAME;
+import static org.onosproject.k8snode.api.Constants.EXTERNAL_TO_ROUTER;
+import static org.onosproject.k8snode.api.K8sApiConfig.Mode.PASSTHROUGH;
import static org.onosproject.k8snode.api.K8sNode.Type.MASTER;
import static org.onosproject.k8snode.api.K8sNode.Type.MINION;
import static org.onosproject.k8snode.api.K8sNodeService.APP_ID;
@@ -65,6 +70,9 @@
private static final String EXT_GATEWAY_IP = "external.gateway.ip";
private static final String EXT_INTF_NAME = "external.interface.name";
+ private static final String DEFAULT_GATEWAY_IP = "127.0.0.1";
+ private static final String DEFAULT_BRIDGE_IP = "127.0.0.1";
+
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected CoreService coreService;
@@ -80,6 +88,9 @@
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected K8sNodeAdminService k8sNodeAdminService;
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected ExternalNetworkService extNetworkService;
+
private final ExecutorService eventExecutor = newSingleThreadExecutor(
groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
@@ -127,21 +138,32 @@
}
k8sClient.nodes().list().getItems().forEach(n ->
- k8sNodeAdminService.createNode(buildK8sNode(n))
+ k8sNodeAdminService.createNode(buildK8sNode(n, config))
);
}
- private K8sNode buildK8sNode(Node node) {
+ private K8sNode buildK8sNode(Node node, K8sApiConfig config) {
String hostname = node.getMetadata().getName();
IpAddress managementIp = null;
IpAddress dataIp = null;
- for (NodeAddress nodeAddress:node.getStatus().getAddresses()) {
- // we need to consider assigning managementIp and dataIp differently
- // FIXME: ExternalIp is not considered currently
- if (nodeAddress.getType().equals(INTERNAL_IP)) {
- managementIp = IpAddress.valueOf(nodeAddress.getAddress());
- dataIp = IpAddress.valueOf(nodeAddress.getAddress());
+ // pass-through mode: we use host IP as the management and data IP
+ // normal mode: we use K8S node's internal IP as the management and data IP
+ if (config.mode() == PASSTHROUGH) {
+ HostNodesInfo info = config.infos().stream().filter(h -> h.nodes()
+ .contains(hostname)).findAny().orElse(null);
+ if (info == null) {
+ log.error("None of the nodes were found in the host nodes info mapping list");
+ } else {
+ managementIp = info.hostIp();
+ dataIp = info.hostIp();
+ }
+ } else {
+ for (NodeAddress nodeAddress:node.getStatus().getAddresses()) {
+ if (nodeAddress.getType().equals(INTERNAL_IP)) {
+ managementIp = IpAddress.valueOf(nodeAddress.getAddress());
+ dataIp = IpAddress.valueOf(nodeAddress.getAddress());
+ }
}
}
@@ -162,17 +184,37 @@
Map<String, String> annots = node.getMetadata().getAnnotations();
- String extIntf = annots.get(EXT_INTF_NAME);
- String extGatewayIpStr = annots.get(EXT_GATEWAY_IP);
- String extBridgeIpStr = annots.get(EXT_BRIDGE_IP);
+ String extIntf = "";
+ String extGatewayIpStr = DEFAULT_GATEWAY_IP;
+ String extBridgeIpStr = DEFAULT_BRIDGE_IP;
+
+ if (config.mode() == PASSTHROUGH) {
+ extNetworkService.registerNetwork(config.extNetworkCidr());
+ extIntf = EXTERNAL_TO_ROUTER + "-" + config.clusterShortName();
+ IpAddress gatewayIp = extNetworkService.getGatewayIp(config.extNetworkCidr());
+ IpAddress bridgeIp = extNetworkService.allocateIp(config.extNetworkCidr());
+ if (gatewayIp != null) {
+ extGatewayIpStr = gatewayIp.toString();
+ }
+ if (bridgeIp != null) {
+ extBridgeIpStr = bridgeIp.toString();
+ }
+ } else {
+ extIntf = annots.get(EXT_INTF_NAME);
+ extGatewayIpStr = annots.get(EXT_GATEWAY_IP);
+ extBridgeIpStr = annots.get(EXT_BRIDGE_IP);
+ }
return DefaultK8sNode.builder()
+ .clusterName(DEFAULT_CLUSTER_NAME)
.hostname(hostname)
.managementIp(managementIp)
.dataIp(dataIp)
.extIntf(extIntf)
.type(nodeType)
+ .segmentId(config.segmentId())
.state(PRE_ON_BOARD)
+ .mode(config.mode())
.extBridgeIp(IpAddress.valueOf(extBridgeIpStr))
.extGatewayIp(IpAddress.valueOf(extGatewayIpStr))
.podCidr(node.getSpec().getPodCIDR())
@@ -209,6 +251,7 @@
if (checkApiServerConfig(config)) {
K8sApiConfig newConfig = config.updateState(K8sApiConfig.State.CONNECTED);
k8sApiConfigAdminService.updateApiConfig(newConfig);
+
bootstrapK8sNodes(config);
}
}
diff --git a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DefaultK8sNodeHandler.java b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DefaultK8sNodeHandler.java
index e3ca070..7e99f47 100644
--- a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DefaultK8sNodeHandler.java
+++ b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DefaultK8sNodeHandler.java
@@ -42,7 +42,7 @@
import org.onosproject.net.behaviour.PatchDescription;
import org.onosproject.net.behaviour.TunnelDescription;
import org.onosproject.net.behaviour.TunnelEndPoints;
-import org.onosproject.net.behaviour.TunnelKeys;
+import org.onosproject.net.behaviour.TunnelKey;
import org.onosproject.net.device.DeviceAdminService;
import org.onosproject.net.device.DeviceEvent;
import org.onosproject.net.device.DeviceListener;
@@ -68,19 +68,10 @@
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static org.onlab.packet.TpPort.tpPort;
import static org.onlab.util.Tools.groupedThreads;
-import static org.onosproject.k8snode.api.Constants.EXTERNAL_BRIDGE;
import static org.onosproject.k8snode.api.Constants.GENEVE;
-import static org.onosproject.k8snode.api.Constants.GENEVE_TUNNEL;
import static org.onosproject.k8snode.api.Constants.GRE;
-import static org.onosproject.k8snode.api.Constants.GRE_TUNNEL;
-import static org.onosproject.k8snode.api.Constants.INTEGRATION_BRIDGE;
-import static org.onosproject.k8snode.api.Constants.INTEGRATION_TO_EXTERNAL_BRIDGE;
-import static org.onosproject.k8snode.api.Constants.INTEGRATION_TO_LOCAL_BRIDGE;
-import static org.onosproject.k8snode.api.Constants.LOCAL_BRIDGE;
-import static org.onosproject.k8snode.api.Constants.LOCAL_TO_INTEGRATION_BRIDGE;
-import static org.onosproject.k8snode.api.Constants.PHYSICAL_EXTERNAL_BRIDGE;
import static org.onosproject.k8snode.api.Constants.VXLAN;
-import static org.onosproject.k8snode.api.Constants.VXLAN_TUNNEL;
+import static org.onosproject.k8snode.api.K8sApiConfig.Mode.NORMAL;
import static org.onosproject.k8snode.api.K8sNodeService.APP_ID;
import static org.onosproject.k8snode.api.K8sNodeState.COMPLETE;
import static org.onosproject.k8snode.api.K8sNodeState.DEVICE_CREATED;
@@ -196,13 +187,19 @@
return;
}
if (!deviceService.isAvailable(k8sNode.intgBridge())) {
- createBridge(k8sNode, INTEGRATION_BRIDGE, k8sNode.intgBridge());
+ createBridge(k8sNode, k8sNode.intgBridgeName(), k8sNode.intgBridge());
}
if (!deviceService.isAvailable(k8sNode.extBridge())) {
- createBridge(k8sNode, EXTERNAL_BRIDGE, k8sNode.extBridge());
+ createBridge(k8sNode, k8sNode.extBridgeName(), k8sNode.extBridge());
}
if (!deviceService.isAvailable(k8sNode.localBridge())) {
- createBridge(k8sNode, LOCAL_BRIDGE, k8sNode.localBridge());
+ createBridge(k8sNode, k8sNode.localBridgeName(), k8sNode.localBridge());
+ }
+
+ if (k8sNode.mode() == NORMAL) {
+ if (!deviceService.isAvailable(k8sNode.tunBridge())) {
+ createBridge(k8sNode, k8sNode.tunBridgeName(), k8sNode.tunBridge());
+ }
}
}
@@ -217,19 +214,21 @@
// create patch ports between integration and external bridges
createPatchInterfaces(k8sNode);
- if (k8sNode.dataIp() != null &&
- !isIntfEnabled(k8sNode, VXLAN_TUNNEL)) {
- createVxlanTunnelInterface(k8sNode);
- }
+ if (k8sNode.mode() == NORMAL) {
+ if (k8sNode.dataIp() != null &&
+ !isIntfEnabled(k8sNode, k8sNode.vxlanPortName())) {
+ createVxlanTunnelInterface(k8sNode);
+ }
- if (k8sNode.dataIp() != null &&
- !isIntfEnabled(k8sNode, GRE_TUNNEL)) {
- createGreTunnelInterface(k8sNode);
- }
+ if (k8sNode.dataIp() != null &&
+ !isIntfEnabled(k8sNode, k8sNode.grePortName())) {
+ createGreTunnelInterface(k8sNode);
+ }
- if (k8sNode.dataIp() != null &&
- !isIntfEnabled(k8sNode, GENEVE_TUNNEL)) {
- createGeneveTunnelInterface(k8sNode);
+ if (k8sNode.dataIp() != null &&
+ !isIntfEnabled(k8sNode, k8sNode.genevePortName())) {
+ createGeneveTunnelInterface(k8sNode);
+ }
}
} catch (Exception e) {
log.error("Exception occurred because of {}", e);
@@ -324,7 +323,7 @@
* @param k8sNode kubernetes node
*/
private void createVxlanTunnelInterface(K8sNode k8sNode) {
- createTunnelInterface(k8sNode, VXLAN, VXLAN_TUNNEL);
+ createTunnelInterface(k8sNode, VXLAN, k8sNode.vxlanPortName());
}
/**
@@ -333,7 +332,7 @@
* @param k8sNode kubernetes node
*/
private void createGreTunnelInterface(K8sNode k8sNode) {
- createTunnelInterface(k8sNode, GRE, GRE_TUNNEL);
+ createTunnelInterface(k8sNode, GRE, k8sNode.grePortName());
}
/**
@@ -342,7 +341,7 @@
* @param k8sNode kubernetes node
*/
private void createGeneveTunnelInterface(K8sNode k8sNode) {
- createTunnelInterface(k8sNode, GENEVE, GENEVE_TUNNEL);
+ createTunnelInterface(k8sNode, GENEVE, k8sNode.genevePortName());
}
private void createPatchInterfaces(K8sNode k8sNode) {
@@ -355,40 +354,61 @@
// integration bridge -> external bridge
PatchDescription brIntExtPatchDesc =
DefaultPatchDescription.builder()
- .deviceId(INTEGRATION_BRIDGE)
- .ifaceName(INTEGRATION_TO_EXTERNAL_BRIDGE)
- .peer(PHYSICAL_EXTERNAL_BRIDGE)
+ .deviceId(k8sNode.intgBridgeName())
+ .ifaceName(k8sNode.intgToExtPatchPortName())
+ .peer(k8sNode.extToIntgPatchPortName())
+ .build();
+
+ // integration bridge -> tunnel bridge
+ PatchDescription brIntTunPatchDesc =
+ DefaultPatchDescription.builder()
+ .deviceId(k8sNode.intgBridgeName())
+ .ifaceName(k8sNode.intgToTunPatchPortName())
+ .peer(k8sNode.tunToIntgPatchPortName())
.build();
// external bridge -> integration bridge
PatchDescription brExtIntPatchDesc =
DefaultPatchDescription.builder()
- .deviceId(EXTERNAL_BRIDGE)
- .ifaceName(PHYSICAL_EXTERNAL_BRIDGE)
- .peer(INTEGRATION_TO_EXTERNAL_BRIDGE)
+ .deviceId(k8sNode.extBridgeName())
+ .ifaceName(k8sNode.extToIntgPatchPortName())
+ .peer(k8sNode.intgToExtPatchPortName())
.build();
// integration bridge -> local bridge
PatchDescription brIntLocalPatchDesc =
DefaultPatchDescription.builder()
- .deviceId(INTEGRATION_BRIDGE)
- .ifaceName(INTEGRATION_TO_LOCAL_BRIDGE)
- .peer(LOCAL_TO_INTEGRATION_BRIDGE)
- .build();
+ .deviceId(k8sNode.intgBridgeName())
+ .ifaceName(k8sNode.intgToLocalPatchPortName())
+ .peer(k8sNode.localToIntgPatchPortName())
+ .build();
// local bridge -> integration bridge
PatchDescription brLocalIntPatchDesc =
DefaultPatchDescription.builder()
- .deviceId(LOCAL_BRIDGE)
- .ifaceName(LOCAL_TO_INTEGRATION_BRIDGE)
- .peer(INTEGRATION_TO_LOCAL_BRIDGE)
- .build();
+ .deviceId(k8sNode.localBridgeName())
+ .ifaceName(k8sNode.localToIntgPatchPortName())
+ .peer(k8sNode.intgToLocalPatchPortName())
+ .build();
InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class);
- ifaceConfig.addPatchMode(INTEGRATION_TO_EXTERNAL_BRIDGE, brIntExtPatchDesc);
- ifaceConfig.addPatchMode(PHYSICAL_EXTERNAL_BRIDGE, brExtIntPatchDesc);
- ifaceConfig.addPatchMode(INTEGRATION_TO_LOCAL_BRIDGE, brIntLocalPatchDesc);
- ifaceConfig.addPatchMode(LOCAL_TO_INTEGRATION_BRIDGE, brLocalIntPatchDesc);
+ ifaceConfig.addPatchMode(k8sNode.intgToExtPatchPortName(), brIntExtPatchDesc);
+ ifaceConfig.addPatchMode(k8sNode.extToIntgPatchPortName(), brExtIntPatchDesc);
+ ifaceConfig.addPatchMode(k8sNode.intgToLocalPatchPortName(), brIntLocalPatchDesc);
+ ifaceConfig.addPatchMode(k8sNode.localToIntgPatchPortName(), brLocalIntPatchDesc);
+ ifaceConfig.addPatchMode(k8sNode.intgToTunPatchPortName(), brIntTunPatchDesc);
+
+ if (k8sNode.mode() == NORMAL) {
+ // tunnel bridge -> integration bridge
+ PatchDescription brTunIntPatchDesc =
+ DefaultPatchDescription.builder()
+ .deviceId(k8sNode.tunBridgeName())
+ .ifaceName(k8sNode.tunToIntgPatchPortName())
+ .peer(k8sNode.intgToTunPatchPortName())
+ .build();
+
+ ifaceConfig.addPatchMode(k8sNode.tunToIntgPatchPortName(), brTunIntPatchDesc);
+ }
}
/**
@@ -408,7 +428,7 @@
return;
}
- TunnelDescription tunnelDesc = buildTunnelDesc(type, intfName);
+ TunnelDescription tunnelDesc = buildTunnelDesc(k8sNode, type, intfName);
InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class);
ifaceConfig.addTunnelMode(intfName, tunnelDesc);
@@ -420,14 +440,16 @@
* @param type network type
* @return tunnel description
*/
- private TunnelDescription buildTunnelDesc(String type, String intfName) {
+ private TunnelDescription buildTunnelDesc(K8sNode k8sNode,
+ String type, String intfName) {
+ TunnelKey<String> key = new TunnelKey<>(k8sNode.tunnelKey());
if (VXLAN.equals(type) || GRE.equals(type) || GENEVE.equals(type)) {
TunnelDescription.Builder tdBuilder =
DefaultTunnelDescription.builder()
- .deviceId(INTEGRATION_BRIDGE)
+ .deviceId(k8sNode.tunBridgeName())
.ifaceName(intfName)
.remote(TunnelEndPoints.flowTunnelEndpoint())
- .key(TunnelKeys.flowTunnelKey());
+ .key(key);
switch (type) {
case VXLAN:
@@ -457,8 +479,8 @@
* @return true if the given interface is enabled, false otherwise
*/
private boolean isIntfEnabled(K8sNode k8sNode, String intf) {
- return deviceService.isAvailable(k8sNode.intgBridge()) &&
- deviceService.getPorts(k8sNode.intgBridge()).stream()
+ return deviceService.isAvailable(k8sNode.tunBridge()) &&
+ deviceService.getPorts(k8sNode.tunBridge()).stream()
.anyMatch(port -> Objects.equals(
port.annotations().value(PORT_NAME), intf) &&
port.isEnabled());
@@ -504,10 +526,16 @@
log.error("Exception caused during init state checking...");
}
- return k8sNode.intgBridge() != null && k8sNode.extBridge() != null &&
+ boolean result = k8sNode.intgBridge() != null && k8sNode.extBridge() != null &&
deviceService.isAvailable(k8sNode.intgBridge()) &&
deviceService.isAvailable(k8sNode.extBridge()) &&
deviceService.isAvailable(k8sNode.localBridge());
+
+ if (k8sNode.mode() == NORMAL) {
+ return result && deviceService.isAvailable(k8sNode.tunBridge());
+ } else {
+ return result;
+ }
}
private boolean isDeviceCreatedStateDone(K8sNode k8sNode) {
@@ -520,17 +548,19 @@
log.error("Exception caused during init state checking...");
}
- if (k8sNode.dataIp() != null &&
- !isIntfEnabled(k8sNode, VXLAN_TUNNEL)) {
- return false;
- }
- if (k8sNode.dataIp() != null &&
- !isIntfEnabled(k8sNode, GRE_TUNNEL)) {
- return false;
- }
- if (k8sNode.dataIp() != null &&
- !isIntfEnabled(k8sNode, GENEVE_TUNNEL)) {
- return false;
+ if (k8sNode.mode() == NORMAL) {
+ if (k8sNode.dataIp() != null &&
+ !isIntfEnabled(k8sNode, k8sNode.vxlanPortName())) {
+ return false;
+ }
+ if (k8sNode.dataIp() != null &&
+ !isIntfEnabled(k8sNode, k8sNode.grePortName())) {
+ return false;
+ }
+ if (k8sNode.dataIp() != null &&
+ !isIntfEnabled(k8sNode, k8sNode.genevePortName())) {
+ return false;
+ }
}
return true;
@@ -574,13 +604,18 @@
}
// delete integration bridge from the node
- client.dropBridge(INTEGRATION_BRIDGE);
+ client.dropBridge(k8sNode.intgBridgeName());
// delete external bridge from the node
- client.dropBridge(EXTERNAL_BRIDGE);
+ client.dropBridge(k8sNode.extBridgeName());
// delete local bridge from the node
- client.dropBridge(LOCAL_BRIDGE);
+ client.dropBridge(k8sNode.localBridgeName());
+
+ if (k8sNode.mode() == NORMAL) {
+ // delete tunnel bridge from the node
+ client.dropBridge(k8sNode.tunBridgeName());
+ }
// disconnect ovsdb
client.disconnect();
@@ -711,9 +746,9 @@
Port port = event.port();
String portName = port.annotations().value(PORT_NAME);
if (k8sNode.state() == DEVICE_CREATED && (
- Objects.equals(portName, VXLAN_TUNNEL) ||
- Objects.equals(portName, GRE_TUNNEL) ||
- Objects.equals(portName, GENEVE_TUNNEL))) {
+ Objects.equals(portName, k8sNode.vxlanPortName()) ||
+ Objects.equals(portName, k8sNode.grePortName()) ||
+ Objects.equals(portName, k8sNode.genevePortName()))) {
log.info("Interface {} added or updated to {}",
portName, device.id());
bootstrapNode(k8sNode);
@@ -736,9 +771,9 @@
Port port = event.port();
String portName = port.annotations().value(PORT_NAME);
if (k8sNode.state() == COMPLETE && (
- Objects.equals(portName, VXLAN_TUNNEL) ||
- Objects.equals(portName, GRE_TUNNEL) ||
- Objects.equals(portName, GENEVE_TUNNEL))) {
+ Objects.equals(portName, k8sNode.vxlanPortName()) ||
+ Objects.equals(portName, k8sNode.grePortName()) ||
+ Objects.equals(portName, k8sNode.genevePortName()))) {
log.warn("Interface {} removed from {}",
portName, event.subject().id());
setState(k8sNode, INCOMPLETE);
diff --git a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DistributedK8sApiConfigStore.java b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DistributedK8sApiConfigStore.java
index e5a65ab..044d9ae 100644
--- a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DistributedK8sApiConfigStore.java
+++ b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DistributedK8sApiConfigStore.java
@@ -20,7 +20,9 @@
import org.onlab.util.KryoNamespace;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
+import org.onosproject.k8snode.api.DefaultHostNodesInfo;
import org.onosproject.k8snode.api.DefaultK8sApiConfig;
+import org.onosproject.k8snode.api.HostNodesInfo;
import org.onosproject.k8snode.api.K8sApiConfig;
import org.onosproject.k8snode.api.K8sApiConfigEvent;
import org.onosproject.k8snode.api.K8sApiConfigStore;
@@ -72,8 +74,11 @@
.register(KryoNamespaces.API)
.register(K8sApiConfig.class)
.register(DefaultK8sApiConfig.class)
+ .register(K8sApiConfig.Mode.class)
.register(K8sApiConfig.Scheme.class)
.register(K8sApiConfig.State.class)
+ .register(HostNodesInfo.class)
+ .register(DefaultHostNodesInfo.class)
.register(Collection.class)
.build();
diff --git a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DistributedK8sHostStore.java b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DistributedK8sHostStore.java
new file mode 100644
index 0000000..17b9fde
--- /dev/null
+++ b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DistributedK8sHostStore.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2020-present Open Networking Foundation
+ *
+ * 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.k8snode.impl;
+
+import com.google.common.collect.ImmutableSet;
+import org.onlab.packet.IpAddress;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.k8snode.api.DefaultK8sHost;
+import org.onosproject.k8snode.api.K8sHost;
+import org.onosproject.k8snode.api.K8sHostEvent;
+import org.onosproject.k8snode.api.K8sHostState;
+import org.onosproject.k8snode.api.K8sHostStore;
+import org.onosproject.k8snode.api.K8sHostStoreDelegate;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.k8snode.api.K8sHostEvent.Type.K8S_HOST_COMPLETE;
+import static org.onosproject.k8snode.api.K8sHostEvent.Type.K8S_HOST_CREATED;
+import static org.onosproject.k8snode.api.K8sHostEvent.Type.K8S_HOST_INCOMPLETE;
+import static org.onosproject.k8snode.api.K8sHostEvent.Type.K8S_HOST_REMOVED;
+import static org.onosproject.k8snode.api.K8sHostEvent.Type.K8S_HOST_UPDATED;
+import static org.onosproject.k8snode.api.K8sHostEvent.Type.K8S_NODES_ADDED;
+import static org.onosproject.k8snode.api.K8sHostEvent.Type.K8S_NODES_REMOVED;
+import static org.onosproject.k8snode.api.K8sHostState.COMPLETE;
+import static org.onosproject.k8snode.api.K8sHostState.INCOMPLETE;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of kubernetes host store using consistent map.
+ */
+@Component(immediate = true, service = K8sHostStore.class)
+public class DistributedK8sHostStore
+ extends AbstractStore<K8sHostEvent, K8sHostStoreDelegate>
+ implements K8sHostStore {
+
+ private final Logger log = getLogger(getClass());
+
+ private static final String ERR_NOT_FOUND = " does not exist";
+ private static final String ERR_DUPLICATE = " already exists";
+ private static final String APP_ID = "org.onosproject.k8snode";
+
+ private static final KryoNamespace
+ SERIALIZER_K8S_HOST = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(K8sHost.class)
+ .register(DefaultK8sHost.class)
+ .register(K8sHostState.class)
+ .register(Collection.class)
+ .build();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected StorageService storageService;
+
+ private final ExecutorService eventExecutor = newSingleThreadExecutor(
+ groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+
+ private final MapEventListener<String, K8sHost> hostMapListener =
+ new K8sHostMapListener();
+ private ConsistentMap<String, K8sHost> hostStore;
+
+ @Activate
+ protected void activate() {
+ ApplicationId appId = coreService.registerApplication(APP_ID);
+ hostStore = storageService.<String, K8sHost>consistentMapBuilder()
+ .withSerializer(Serializer.using(SERIALIZER_K8S_HOST))
+ .withName("k8s-hoststore")
+ .withApplicationId(appId)
+ .build();
+ hostStore.addListener(hostMapListener);
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ hostStore.removeListener(hostMapListener);
+ eventExecutor.shutdown();
+ log.info("Stopped");
+ }
+
+ @Override
+ public void createHost(K8sHost host) {
+ hostStore.compute(host.hostIp().toString(), (hostIp, existing) -> {
+ final String error = host.hostIp().toString() + ERR_DUPLICATE;
+ checkArgument(existing == null, error);
+ return host;
+ });
+ }
+
+ @Override
+ public void updateHost(K8sHost host) {
+ hostStore.compute(host.hostIp().toString(), (hostIp, existing) -> {
+ final String error = host.hostIp().toString() + ERR_NOT_FOUND;
+ checkArgument(existing != null, error);
+ return host;
+ });
+ }
+
+ @Override
+ public K8sHost removeHost(IpAddress hostIp) {
+ Versioned<K8sHost> host = hostStore.remove(hostIp.toString());
+ if (host == null) {
+ final String error = hostIp.toString() + ERR_NOT_FOUND;
+ throw new IllegalArgumentException(error);
+ }
+ return host.value();
+ }
+
+ @Override
+ public Set<K8sHost> hosts() {
+ return ImmutableSet.copyOf(hostStore.asJavaMap().values());
+ }
+
+ @Override
+ public K8sHost host(IpAddress hostIp) {
+ return hostStore.asJavaMap().get(hostIp.toString());
+ }
+
+ private class K8sHostMapListener
+ implements MapEventListener<String, K8sHost> {
+
+ @Override
+ public void event(MapEvent<String, K8sHost> event) {
+ switch (event.type()) {
+ case INSERT:
+ log.debug("Kubernetes host created {}", event.newValue());
+ eventExecutor.execute(() ->
+ notifyDelegate(new K8sHostEvent(
+ K8S_HOST_CREATED, event.newValue().value()
+ )));
+ break;
+ case UPDATE:
+ log.debug("Kubernetes host updated {}", event.newValue());
+ eventExecutor.execute(() -> {
+ notifyDelegate(new K8sHostEvent(
+ K8S_HOST_UPDATED, event.newValue().value()
+ ));
+
+ if (event.newValue().value().state() == COMPLETE) {
+ notifyDelegate(new K8sHostEvent(
+ K8S_HOST_COMPLETE,
+ event.newValue().value()
+ ));
+ } else if (event.newValue().value().state() == INCOMPLETE) {
+ notifyDelegate(new K8sHostEvent(
+ K8S_HOST_INCOMPLETE,
+ event.newValue().value()
+ ));
+ }
+
+ K8sHost origHost = event.newValue().value();
+ Set<String> oldNodes = event.oldValue().value().nodeNames();
+ Set<String> newNodes = event.newValue().value().nodeNames();
+
+ Set<String> addedNodes = new HashSet<>(newNodes);
+ Set<String> removedNodes = new HashSet<>(oldNodes);
+
+ addedNodes.removeAll(oldNodes);
+ removedNodes.removeAll(newNodes);
+
+ if (addedNodes.size() > 0) {
+ K8sHost addedHost = DefaultK8sHost.builder()
+ .hostIp(origHost.hostIp())
+ .state(origHost.state())
+ .nodeNames(addedNodes)
+ .build();
+ notifyDelegate(new K8sHostEvent(K8S_NODES_ADDED, addedHost));
+ }
+
+ if (removedNodes.size() > 0) {
+ K8sHost removedHost = DefaultK8sHost.builder()
+ .hostIp(origHost.hostIp())
+ .state(origHost.state())
+ .nodeNames(removedNodes)
+ .build();
+ notifyDelegate(new K8sHostEvent(K8S_NODES_REMOVED, removedHost));
+ }
+ });
+ break;
+ case REMOVE:
+ log.debug("Kubernetes host removed {}", event.oldValue());
+ eventExecutor.execute(() ->
+ notifyDelegate(new K8sHostEvent(
+ K8S_HOST_REMOVED, event.oldValue().value()
+ )));
+ break;
+ default:
+ // do nothing
+ break;
+ }
+ }
+ }
+}
diff --git a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DistributedK8sNodeStore.java b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DistributedK8sNodeStore.java
index 15d2697..8d3e926 100644
--- a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DistributedK8sNodeStore.java
+++ b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DistributedK8sNodeStore.java
@@ -19,7 +19,10 @@
import org.onlab.util.KryoNamespace;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
+import org.onosproject.k8snode.api.DefaultK8sExternalNetwork;
import org.onosproject.k8snode.api.DefaultK8sNode;
+import org.onosproject.k8snode.api.K8sApiConfig;
+import org.onosproject.k8snode.api.K8sExternalNetwork;
import org.onosproject.k8snode.api.K8sNode;
import org.onosproject.k8snode.api.K8sNodeEvent;
import org.onosproject.k8snode.api.K8sNodeState;
@@ -77,6 +80,9 @@
.register(DefaultK8sNode.class)
.register(K8sNode.Type.class)
.register(K8sNodeState.class)
+ .register(K8sApiConfig.Mode.class)
+ .register(K8sExternalNetwork.class)
+ .register(DefaultK8sExternalNetwork.class)
.register(Collection.class)
.build();
diff --git a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/ExternalNetworkManager.java b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/ExternalNetworkManager.java
new file mode 100644
index 0000000..184998a
--- /dev/null
+++ b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/ExternalNetworkManager.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2020-present Open Networking Foundation
+ *
+ * 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.k8snode.impl;
+
+import org.apache.commons.net.util.SubnetUtils;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.k8snode.api.ExternalNetworkService;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.k8snode.api.K8sNodeService.APP_ID;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * External network service implementation.
+ */
+@Component(
+ immediate = true,
+ service = { ExternalNetworkService.class }
+)
+public class ExternalNetworkManager implements ExternalNetworkService {
+
+ private final Logger log = getLogger(getClass());
+
+ private static final KryoNamespace
+ SERIALIZER_EXTERNAL_NETWORK = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .build();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected StorageService storageService;
+
+ private final ExecutorService eventExecutor = newSingleThreadExecutor(
+ groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+
+ private ConsistentMap<String, Set<String>> networkIpPool;
+
+ @Activate
+ protected void activate() {
+ ApplicationId appId = coreService.registerApplication(APP_ID);
+ networkIpPool = storageService.<String, Set<String>>consistentMapBuilder()
+ .withSerializer(Serializer.using(SERIALIZER_EXTERNAL_NETWORK))
+ .withName("external-network-ip-pool")
+ .withApplicationId(appId)
+ .build();
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ eventExecutor.shutdown();
+ log.info("Stopped");
+ }
+
+ @Override
+ public void registerNetwork(IpPrefix cidr) {
+ if (!networkIpPool.containsKey(cidr.toString())) {
+ SubnetUtils utils = new SubnetUtils(cidr.toString());
+ utils.setInclusiveHostCount(false);
+ SubnetUtils.SubnetInfo info = utils.getInfo();
+
+ Set<String> all = Arrays.stream(info.getAllAddresses())
+ .collect(Collectors.toSet());
+ all.remove(info.getNetworkAddress());
+ all.remove(info.getHighAddress());
+ all.remove(info.getLowAddress());
+ all.remove(info.getBroadcastAddress());
+
+ networkIpPool.put(cidr.toString(), all);
+ }
+ }
+
+ @Override
+ public void unregisterNetwork(IpPrefix cidr) {
+ if (!networkIpPool.containsKey(cidr.toString())) {
+ log.warn("The given network {} is not found!", cidr.toString());
+ } else {
+ networkIpPool.remove(cidr.toString());
+ }
+ }
+
+ @Override
+ public IpAddress getGatewayIp(IpPrefix cidr) {
+ SubnetUtils utils = new SubnetUtils(cidr.toString());
+ utils.setInclusiveHostCount(false);
+ SubnetUtils.SubnetInfo info = utils.getInfo();
+
+ return IpAddress.valueOf(info.getLowAddress());
+ }
+
+ @Override
+ public IpAddress allocateIp(IpPrefix cidr) {
+ if (!networkIpPool.containsKey(cidr.toString())) {
+ log.error("The given network {} is not found", cidr.toString());
+ return null;
+ } else {
+ Set<String> pool = networkIpPool.get(cidr.toString()).value();
+ String ipStr = pool.stream().findFirst().orElse(null);
+ if (ipStr == null) {
+ log.error("No IPs are found in the given network {}", cidr.toString());
+ return null;
+ }
+
+ pool.remove(ipStr);
+ networkIpPool.put(cidr.toString(), pool);
+ return IpAddress.valueOf(ipStr);
+ }
+ }
+
+ @Override
+ public void releaseIp(IpPrefix cidr, IpAddress ip) {
+ if (!networkIpPool.containsKey(cidr.toString())) {
+ log.error("The given network {} is not found", cidr.toString());
+ } else {
+ Set<String> pool = networkIpPool.get(cidr.toString()).value();
+ pool.add(ip.toString());
+ networkIpPool.put(cidr.toString(), pool);
+ }
+ }
+
+ @Override
+ public Set<String> getAllIps(IpPrefix cidr) {
+ return networkIpPool.get(cidr.toString()).value();
+ }
+}
diff --git a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/K8sHostManager.java b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/K8sHostManager.java
new file mode 100644
index 0000000..fd26763
--- /dev/null
+++ b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/K8sHostManager.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2020-present Open Networking Foundation
+ *
+ * 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.k8snode.impl;
+
+import com.google.common.collect.ImmutableSet;
+import org.onlab.packet.IpAddress;
+import org.onlab.util.Tools;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.event.ListenerRegistry;
+import org.onosproject.k8snode.api.K8sHost;
+import org.onosproject.k8snode.api.K8sHostAdminService;
+import org.onosproject.k8snode.api.K8sHostEvent;
+import org.onosproject.k8snode.api.K8sHostListener;
+import org.onosproject.k8snode.api.K8sHostService;
+import org.onosproject.k8snode.api.K8sHostStore;
+import org.onosproject.k8snode.api.K8sHostStoreDelegate;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.store.service.StorageService;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.util.Dictionary;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.k8snode.api.K8sHostState.COMPLETE;
+import static org.onosproject.k8snode.impl.OsgiPropertyConstants.OVSDB_PORT;
+import static org.onosproject.k8snode.impl.OsgiPropertyConstants.OVSDB_PORT_NUM_DEFAULT;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Service administering the inventory of kubernetes hosts.
+ */
+@Component(
+ immediate = true,
+ service = {K8sHostService.class, K8sHostAdminService.class},
+ property = {
+ OVSDB_PORT + ":Integer=" + OVSDB_PORT_NUM_DEFAULT
+ }
+)
+public class K8sHostManager
+ extends ListenerRegistry<K8sHostEvent, K8sHostListener>
+ implements K8sHostService, K8sHostAdminService {
+
+ private final Logger log = getLogger(getClass());
+
+ private static final String MSG_HOST = "Kubernetes host %s %s";
+ private static final String MSG_CREATED = "created";
+ private static final String MSG_UPDATED = "updated";
+ private static final String MSG_REMOVED = "removed";
+
+ private static final String ERR_NULL_HOST = "Kubernetes host cannot be null";
+ private static final String ERR_NULL_HOST_IP = "Kubernetes host IP cannot be null";
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected K8sHostStore hostStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected LeadershipService leadershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected DeviceService deviceService;
+
+ /** OVSDB server listen port. */
+ private int ovsdbPortNum = OVSDB_PORT_NUM_DEFAULT;
+
+ private final ExecutorService eventExecutor = newSingleThreadExecutor(
+ groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+
+ private final K8sHostStoreDelegate delegate = new K8sHostManager.InternalHostStoreDelegate();
+
+ private ApplicationId appId;
+
+ @Activate
+ protected void activate() {
+ appId = coreService.registerApplication(APP_ID);
+ hostStore.setDelegate(delegate);
+
+ leadershipService.runForLeadership(appId.name());
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ hostStore.unsetDelegate(delegate);
+
+ leadershipService.withdraw(appId.name());
+ eventExecutor.shutdown();
+
+ log.info("Stopped");
+ }
+
+ @Modified
+ protected void modified(ComponentContext context) {
+ Dictionary<?, ?> properties = context.getProperties();
+ int updatedOvsdbPort = Tools.getIntegerProperty(properties, OVSDB_PORT);
+ if (!Objects.equals(updatedOvsdbPort, ovsdbPortNum)) {
+ ovsdbPortNum = updatedOvsdbPort;
+ }
+
+ log.info("Modified");
+ }
+
+ @Override
+ public void createHost(K8sHost host) {
+ checkNotNull(host, ERR_NULL_HOST);
+
+ hostStore.createHost(host);
+
+ log.info(String.format(MSG_HOST, host.hostIp().toString(), MSG_CREATED));
+ }
+
+ @Override
+ public void updateHost(K8sHost host) {
+ checkNotNull(host, ERR_NULL_HOST);
+
+ hostStore.updateHost(host);
+
+ log.info(String.format(MSG_HOST, host.hostIp().toString(), MSG_UPDATED));
+ }
+
+ @Override
+ public K8sHost removeHost(IpAddress hostIp) {
+ checkArgument(hostIp != null, ERR_NULL_HOST_IP);
+
+ K8sHost host = hostStore.removeHost(hostIp);
+ log.info(String.format(MSG_HOST, hostIp.toString(), MSG_REMOVED));
+
+ return host;
+ }
+
+ @Override
+ public Set<K8sHost> hosts() {
+ return hostStore.hosts();
+ }
+
+ @Override
+ public Set<K8sHost> completeHosts() {
+ Set<K8sHost> hosts = hostStore.hosts().stream()
+ .filter(h -> Objects.equals(h.state(), COMPLETE))
+ .collect(Collectors.toSet());
+ return ImmutableSet.copyOf(hosts);
+ }
+
+ @Override
+ public K8sHost host(IpAddress hostIp) {
+ return hostStore.hosts().stream()
+ .filter(h -> Objects.equals(h.hostIp(), hostIp))
+ .findFirst().orElse(null);
+ }
+
+ private class InternalHostStoreDelegate implements K8sHostStoreDelegate {
+
+ @Override
+ public void notify(K8sHostEvent event) {
+ if (event != null) {
+ log.trace("send kubernetes host event {}", event);
+ process(event);
+ }
+ }
+ }
+}
diff --git a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/K8sNodeManager.java b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/K8sNodeManager.java
index 5b3f353..bc789d3 100644
--- a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/K8sNodeManager.java
+++ b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/K8sNodeManager.java
@@ -55,6 +55,7 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.k8snode.api.K8sApiConfig.Mode.NORMAL;
import static org.onosproject.k8snode.api.K8sNodeState.COMPLETE;
import static org.onosproject.k8snode.impl.OsgiPropertyConstants.OVSDB_PORT;
import static org.onosproject.k8snode.impl.OsgiPropertyConstants.OVSDB_PORT_NUM_DEFAULT;
@@ -159,6 +160,7 @@
K8sNode intNode;
K8sNode extNode;
K8sNode localNode;
+ K8sNode tunNode;
if (node.intgBridge() == null) {
String deviceIdStr = genDpid(deviceIdCounter.incrementAndGet());
@@ -196,7 +198,24 @@
NOT_DUPLICATED_MSG, localNode.localBridge());
}
- nodeStore.createNode(localNode);
+ if (node.mode() == NORMAL) {
+ if (node.tunBridge() == null) {
+ String deviceIdStr = genDpid(deviceIdCounter.incrementAndGet());
+ checkNotNull(deviceIdStr, ERR_NULL_DEVICE_ID);
+ tunNode = localNode.updateTunBridge(DeviceId.deviceId(deviceIdStr));
+ checkArgument(!hasTunBridge(tunNode.tunBridge(), tunNode.hostname()),
+ NOT_DUPLICATED_MSG, tunNode.tunBridge());
+ } else {
+ tunNode = localNode;
+ checkArgument(!hasTunBridge(tunNode.tunBridge(), tunNode.hostname()),
+ NOT_DUPLICATED_MSG, tunNode.tunBridge());
+ }
+
+ nodeStore.createNode(tunNode);
+ } else {
+ nodeStore.createNode(localNode);
+ }
+
log.info(String.format(MSG_NODE, extNode.hostname(), MSG_CREATED));
}
@@ -207,6 +226,7 @@
K8sNode intNode;
K8sNode extNode;
K8sNode localNode;
+ K8sNode tunNode;
K8sNode existingNode = nodeStore.node(node.hostname());
checkNotNull(existingNode, ERR_NULL_NODE);
@@ -247,7 +267,22 @@
NOT_DUPLICATED_MSG, localNode.localBridge());
}
- nodeStore.updateNode(localNode);
+ if (node.mode() == NORMAL) {
+ DeviceId existTunBridge = nodeStore.node(node.hostname()).tunBridge();
+
+ if (localNode.tunBridge() == null) {
+ tunNode = localNode.updateTunBridge(existTunBridge);
+ checkArgument(!hasTunBridge(tunNode.tunBridge(), tunNode.hostname()),
+ NOT_DUPLICATED_MSG, tunNode.tunBridge());
+ } else {
+ tunNode = localNode;
+ checkArgument(!hasTunBridge(tunNode.tunBridge(), tunNode.hostname()),
+ NOT_DUPLICATED_MSG, tunNode.tunBridge());
+ }
+ nodeStore.updateNode(tunNode);
+ } else {
+ nodeStore.updateNode(localNode);
+ }
log.info(String.format(MSG_NODE, extNode.hostname(), MSG_UPDATED));
}
@@ -265,6 +300,13 @@
}
@Override
+ public Set<K8sNode> nodes(String clusterName) {
+ return nodeStore.nodes().stream()
+ .filter(n -> n.clusterName().equals(clusterName))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
public Set<K8sNode> nodes(Type type) {
Set<K8sNode> nodes = nodeStore.nodes().stream()
.filter(node -> Objects.equals(node.type(), type))
@@ -330,6 +372,15 @@
return existNode.isPresent();
}
+ private boolean hasTunBridge(DeviceId deviceId, String hostname) {
+ Optional<K8sNode> existNode = nodeStore.nodes().stream()
+ .filter(n -> !n.hostname().equals(hostname))
+ .filter(n -> deviceId.equals(n.tunBridge()))
+ .findFirst();
+
+ return existNode.isPresent();
+ }
+
private class InternalNodeStoreDelegate implements K8sNodeStoreDelegate {
@Override
diff --git a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/web/K8sNodeCodecRegister.java b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/web/K8sNodeCodecRegister.java
index 9138539..4bca46e 100644
--- a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/web/K8sNodeCodecRegister.java
+++ b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/web/K8sNodeCodecRegister.java
@@ -16,9 +16,13 @@
package org.onosproject.k8snode.web;
import org.onosproject.codec.CodecService;
+import org.onosproject.k8snode.api.HostNodesInfo;
import org.onosproject.k8snode.api.K8sApiConfig;
+import org.onosproject.k8snode.api.K8sHost;
import org.onosproject.k8snode.api.K8sNode;
+import org.onosproject.k8snode.codec.HostNodesInfoCodec;
import org.onosproject.k8snode.codec.K8sApiConfigCodec;
+import org.onosproject.k8snode.codec.K8sHostCodec;
import org.onosproject.k8snode.codec.K8sNodeCodec;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
@@ -45,6 +49,8 @@
codecService.registerCodec(K8sNode.class, new K8sNodeCodec());
codecService.registerCodec(K8sApiConfig.class, new K8sApiConfigCodec());
+ codecService.registerCodec(HostNodesInfo.class, new HostNodesInfoCodec());
+ codecService.registerCodec(K8sHost.class, new K8sHostCodec());
log.info("Started");
}
@@ -54,6 +60,8 @@
codecService.unregisterCodec(K8sNode.class);
codecService.unregisterCodec(K8sApiConfig.class);
+ codecService.unregisterCodec(HostNodesInfo.class);
+ codecService.unregisterCodec(K8sHost.class);
log.info("Stopped");
}
diff --git a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/web/K8sNodeWebResource.java b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/web/K8sNodeWebResource.java
index df4f63f..1b20fd4 100644
--- a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/web/K8sNodeWebResource.java
+++ b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/web/K8sNodeWebResource.java
@@ -19,8 +19,11 @@
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Sets;
+import org.onlab.packet.IpAddress;
import org.onosproject.k8snode.api.K8sApiConfig;
import org.onosproject.k8snode.api.K8sApiConfigAdminService;
+import org.onosproject.k8snode.api.K8sHost;
+import org.onosproject.k8snode.api.K8sHostAdminService;
import org.onosproject.k8snode.api.K8sNode;
import org.onosproject.k8snode.api.K8sNodeAdminService;
import org.onosproject.k8snode.api.K8sNodeState;
@@ -42,6 +45,7 @@
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.io.InputStream;
+import java.util.HashSet;
import java.util.Set;
import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
@@ -61,11 +65,15 @@
private final Logger log = LoggerFactory.getLogger(getClass());
private static final String MESSAGE_NODE = "Received node %s request";
+ private static final String MESSAGE_HOST = "Received host %s request";
private static final String NODES = "nodes";
private static final String API_CONFIGS = "apiConfigs";
+ private static final String HOSTS = "hosts";
+ private static final String NODE_NAMES = "nodeNames";
private static final String CREATE = "CREATE";
private static final String UPDATE = "UPDATE";
private static final String NODE_ID = "NODE_ID";
+ private static final String HOST_IP = "HOST_IP";
private static final String REMOVE = "REMOVE";
private static final String QUERY = "QUERY";
private static final String INIT = "INIT";
@@ -78,6 +86,7 @@
private static final String ERROR_MESSAGE = " cannot be null";
private final K8sNodeAdminService nodeAdminService = get(K8sNodeAdminService.class);
+ private final K8sHostAdminService hostAdminService = get(K8sHostAdminService.class);
private final K8sApiConfigAdminService configAdminService = get(K8sApiConfigAdminService.class);
@Context
@@ -155,8 +164,8 @@
public Response deleteNodes(@PathParam("hostname") String hostname) {
log.trace(String.format(MESSAGE_NODE, REMOVE));
- K8sNode existing =
- nodeAdminService.node(nullIsIllegal(hostname, HOST_NAME + ERROR_MESSAGE));
+ K8sNode existing = nodeAdminService.node(
+ nullIsIllegal(hostname, HOST_NAME + ERROR_MESSAGE));
if (existing == null) {
log.warn("There is no node configuration to delete : {}", hostname);
@@ -293,30 +302,6 @@
return ok(mapper().createObjectNode().put(RESULT, result)).build();
}
- private Set<K8sNode> readNodeConfiguration(InputStream input) {
- Set<K8sNode> nodeSet = Sets.newHashSet();
- try {
- JsonNode jsonTree = readTreeFromStream(mapper().enable(INDENT_OUTPUT), input);
- ArrayNode nodes = (ArrayNode) jsonTree.path(NODES);
- nodes.forEach(node -> {
- try {
- ObjectNode objectNode = node.deepCopy();
- K8sNode k8sNode =
- codec(K8sNode.class).decode(objectNode, this);
-
- nodeSet.add(k8sNode);
- } catch (Exception e) {
- log.error("Exception occurred due to {}", e);
- throw new IllegalArgumentException();
- }
- });
- } catch (Exception e) {
- throw new IllegalArgumentException(e);
- }
-
- return nodeSet;
- }
-
/**
* Creates a set of kubernetes API config from the JSON input stream.
*
@@ -387,8 +372,8 @@
public Response deleteApiConfig(@PathParam("endpoint") String endpoint) {
log.trace(String.format(MESSAGE_NODE, REMOVE));
- K8sApiConfig existing =
- configAdminService.apiConfig(nullIsIllegal(endpoint, ENDPOINT + ERROR_MESSAGE));
+ K8sApiConfig existing = configAdminService.apiConfig(
+ nullIsIllegal(endpoint, ENDPOINT + ERROR_MESSAGE));
if (existing == null) {
log.warn("There is no API configuration to delete : {}", endpoint);
@@ -400,6 +385,139 @@
return Response.noContent().build();
}
+ /**
+ * Creates a set of kubernetes hosts' config from the JSON input stream.
+ *
+ * @param input kubernetes hosts JSON input stream
+ * @return 201 CREATED if the JSON is correct, 400 BAD_REQUEST if the JSON
+ * is malformed
+ * @onos.rsModel K8sHosts
+ */
+ @POST
+ @Path("host")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response createHosts(InputStream input) {
+ log.trace(String.format(MESSAGE_NODE, CREATE));
+
+ readHostsConfiguration(input).forEach(host -> {
+ K8sHost existing = hostAdminService.host(host.hostIp());
+ if (existing == null) {
+ hostAdminService.createHost(host);
+ }
+ });
+
+ UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+ .path(HOSTS)
+ .path(HOST_IP);
+
+ return created(locationBuilder.build()).build();
+ }
+
+ /**
+ * Add a set of new nodes into the existing host.
+ *
+ * @param hostIp host IP address
+ * @param input kubernetes node names JSON input stream
+ * @return 200 UPDATED if the JSON is correct, 400 BAD_REQUEST if the JSON
+ * is malformed
+ * @onos.rsModel K8sNodeNames
+ */
+ @PUT
+ @Path("host/add/nodes/{hostIp}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response addNodesToHost(@PathParam("hostIp") String hostIp,
+ InputStream input) {
+ log.trace(String.format(MESSAGE_HOST, UPDATE));
+
+ Set<String> newNodeNames = readNodeNamesConfiguration(input);
+ K8sHost host = hostAdminService.host(IpAddress.valueOf(hostIp));
+ Set<String> existNodeNames = host.nodeNames();
+ existNodeNames.addAll(newNodeNames);
+ K8sHost updated = host.updateNodeNames(existNodeNames);
+ hostAdminService.updateHost(updated);
+ return Response.ok().build();
+ }
+
+ /**
+ * Remove a set of new nodes from the existing host.
+ *
+ * @param hostIp host IP address
+ * @param input kubernetes node names JSON input stream
+ * @return 200 UPDATED if the JSON is correct, 400 BAD_REQUEST if the JSON
+ * is malformed
+ * @onos.rsModel K8sNodeNames
+ */
+ @PUT
+ @Path("host/delete/nodes/{hostIp}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response removeNodesFromHost(@PathParam("hostIp") String hostIp,
+ InputStream input) {
+ log.trace(String.format(MESSAGE_HOST, UPDATE));
+
+ Set<String> newNodeNames = readNodeNamesConfiguration(input);
+ K8sHost host = hostAdminService.host(IpAddress.valueOf(hostIp));
+ Set<String> existNodeNames = host.nodeNames();
+ existNodeNames.removeAll(newNodeNames);
+ K8sHost updated = host.updateNodeNames(existNodeNames);
+ hostAdminService.updateHost(updated);
+ return Response.ok().build();
+ }
+
+ /**
+ * Removes a kubernetes host' config.
+ *
+ * @param hostIp host IP contained in kubernetes nodes configuration
+ * @return 204 NO_CONTENT, 400 BAD_REQUEST if the JSON is malformed, and
+ * 304 NOT_MODIFIED without the updated config
+ */
+ @DELETE
+ @Path("host/{hostIp}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response deleteHost(@PathParam("hostIp") String hostIp) {
+ log.trace(String.format(MESSAGE_HOST, REMOVE));
+
+ K8sHost existing = hostAdminService.host(IpAddress.valueOf(
+ nullIsIllegal(hostIp, HOST_IP + ERROR_MESSAGE)));
+
+ if (existing == null) {
+ log.warn("There is no host configuration to delete : {}", hostIp);
+ return Response.notModified().build();
+ } else {
+ hostAdminService.removeHost(IpAddress.valueOf(
+ nullIsIllegal(hostIp, HOST_IP + ERROR_MESSAGE)));
+ }
+
+ return Response.noContent().build();
+ }
+
+ private Set<K8sNode> readNodeConfiguration(InputStream input) {
+ Set<K8sNode> nodeSet = Sets.newHashSet();
+ try {
+ JsonNode jsonTree = readTreeFromStream(mapper().enable(INDENT_OUTPUT), input);
+ ArrayNode nodes = (ArrayNode) jsonTree.path(NODES);
+ nodes.forEach(node -> {
+ try {
+ ObjectNode objectNode = node.deepCopy();
+ K8sNode k8sNode =
+ codec(K8sNode.class).decode(objectNode, this);
+
+ nodeSet.add(k8sNode);
+ } catch (Exception e) {
+ log.error("Exception occurred due to {}", e);
+ throw new IllegalArgumentException();
+ }
+ });
+ } catch (Exception e) {
+ throw new IllegalArgumentException(e);
+ }
+
+ return nodeSet;
+ }
+
private Set<K8sApiConfig> readApiConfigConfiguration(InputStream input) {
Set<K8sApiConfig> configSet = Sets.newHashSet();
try {
@@ -423,4 +541,49 @@
return configSet;
}
+
+ private Set<K8sHost> readHostsConfiguration(InputStream input) {
+ Set<K8sHost> hostSet = new HashSet<>();
+ try {
+ JsonNode jsonTree = readTreeFromStream(mapper().enable(INDENT_OUTPUT), input);
+ ArrayNode hosts = (ArrayNode) jsonTree.path(HOSTS);
+ hosts.forEach(host -> {
+ try {
+ ObjectNode objectNode = host.deepCopy();
+ K8sHost k8sHost =
+ codec(K8sHost.class).decode(objectNode, this);
+
+ hostSet.add(k8sHost);
+ } catch (Exception e) {
+ log.error("Exception occurred due to {}", e);
+ throw new IllegalArgumentException();
+ }
+ });
+ } catch (Exception e) {
+ throw new IllegalArgumentException(e);
+ }
+
+ return hostSet;
+ }
+
+ private Set<String> readNodeNamesConfiguration(InputStream input) {
+ Set<String> nodeNames = new HashSet<>();
+ try {
+ JsonNode jsonTree = readTreeFromStream(mapper().enable(INDENT_OUTPUT), input);
+ ArrayNode names = (ArrayNode) jsonTree.path(NODE_NAMES);
+ names.forEach(name -> {
+ try {
+ ObjectNode objectNode = name.deepCopy();
+ nodeNames.add(objectNode.asText());
+ } catch (Exception e) {
+ log.error("Exception occurred due to {}", e);
+ throw new IllegalArgumentException();
+ }
+ });
+ } catch (Exception e) {
+ throw new IllegalArgumentException(e);
+ }
+
+ return nodeNames;
+ }
}
diff --git a/apps/k8s-node/app/src/main/resources/definitions/K8sApiConfig.json b/apps/k8s-node/app/src/main/resources/definitions/K8sApiConfig.json
index b82447f..b8dd0b6 100644
--- a/apps/k8s-node/app/src/main/resources/definitions/K8sApiConfig.json
+++ b/apps/k8s-node/app/src/main/resources/definitions/K8sApiConfig.json
@@ -9,11 +9,21 @@
"items": {
"type": "object",
"required": [
+ "clusterName",
+ "segmentId",
"scheme",
"ipAddress",
"port"
],
"properties": {
+ "clusterName": {
+ "type": "string",
+ "example": "kubernetes"
+ },
+ "segmentId": {
+ "type": "integer",
+ "example": 1
+ },
"scheme": {
"type": "string",
"example": "HTTP"
diff --git a/apps/k8s-node/app/src/main/resources/definitions/K8sHosts.json b/apps/k8s-node/app/src/main/resources/definitions/K8sHosts.json
new file mode 100644
index 0000000..132a0a7
--- /dev/null
+++ b/apps/k8s-node/app/src/main/resources/definitions/K8sHosts.json
@@ -0,0 +1,31 @@
+{
+ "type": "object",
+ "required": [
+ "hosts"
+ ],
+ "properties": {
+ "hosts": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "hostIp",
+ "nodeNames"
+ ],
+ "properties": {
+ "hostIp": {
+ "type": "string",
+ "example": "192.168.200.10"
+ },
+ "nodeNames": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "example": "k8s-master"
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/k8s-node/app/src/main/resources/definitions/K8sNode.json b/apps/k8s-node/app/src/main/resources/definitions/K8sNode.json
index 4351616..2e2cc37 100644
--- a/apps/k8s-node/app/src/main/resources/definitions/K8sNode.json
+++ b/apps/k8s-node/app/src/main/resources/definitions/K8sNode.json
@@ -9,6 +9,7 @@
"items": {
"type": "object",
"required": [
+ "clusterName",
"hostname",
"type",
"managementIp",
@@ -16,6 +17,10 @@
"integrationBridge"
],
"properties": {
+ "clusterName": {
+ "type": "string",
+ "example": "kubernetes"
+ },
"hostname": {
"type": "string",
"example": "host1"
diff --git a/apps/k8s-node/app/src/main/resources/definitions/K8sNodeNames.json b/apps/k8s-node/app/src/main/resources/definitions/K8sNodeNames.json
new file mode 100644
index 0000000..a1ced8f
--- /dev/null
+++ b/apps/k8s-node/app/src/main/resources/definitions/K8sNodeNames.json
@@ -0,0 +1,7 @@
+{
+ "type": "array",
+ "items": {
+ "type": "string",
+ "example": "k8s-master"
+ }
+}
\ No newline at end of file
diff --git a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/HostNodesInfoJsonMatcher.java b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/HostNodesInfoJsonMatcher.java
new file mode 100644
index 0000000..5de7a71
--- /dev/null
+++ b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/HostNodesInfoJsonMatcher.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2020-present Open Networking Foundation
+ *
+ * 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.k8snode.codec;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.onosproject.k8snode.api.HostNodesInfo;
+
+/**
+ * Hamcrest matcher for HostNodesInfo.
+ */
+public final class HostNodesInfoJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
+
+ private final HostNodesInfo hostNodesInfo;
+
+ private static final String HOST_IP = "hostIp";
+ private static final String NODES = "nodes";
+
+ private HostNodesInfoJsonMatcher(HostNodesInfo hostNodesInfo) {
+ this.hostNodesInfo = hostNodesInfo;
+ }
+
+ @Override
+ protected boolean matchesSafely(JsonNode jsonNode, Description description) {
+
+ // check host IP
+ String jsonHostIp = jsonNode.get(HOST_IP).asText();
+ String hostIp = hostNodesInfo.hostIp().toString();
+ if (!jsonHostIp.equals(hostIp)) {
+ description.appendText("host IP was " + jsonHostIp);
+ return false;
+ }
+
+ // check nodes
+ JsonNode jsonNodes = jsonNode.get(NODES);
+ if (jsonNodes.size() != hostNodesInfo.nodes().size()) {
+ description.appendText("Nodes size was " + jsonNodes.size());
+ return false;
+ }
+
+ boolean nodeFound = true;
+ ArrayNode jsonNodeArray = (ArrayNode) jsonNodes;
+ for (JsonNode jsonNodeTmp : jsonNodeArray) {
+ if (!hostNodesInfo.nodes().contains(jsonNodeTmp.asText())) {
+ nodeFound = false;
+ }
+ }
+
+ if (!nodeFound) {
+ description.appendText("Node not found");
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(hostNodesInfo.toString());
+ }
+
+ /**
+ * Factory to allocate a hostNodesInfo matcher.
+ *
+ * @param info host IP address to nodes mapping info
+ * @return matcher
+ */
+ public static HostNodesInfoJsonMatcher matchesHostNodesInfo(HostNodesInfo info) {
+ return new HostNodesInfoJsonMatcher(info);
+ }
+}
diff --git a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sApiConfigCodecTest.java b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sApiConfigCodecTest.java
index cca918e..b5deb38 100644
--- a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sApiConfigCodecTest.java
+++ b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sApiConfigCodecTest.java
@@ -18,20 +18,25 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
import org.junit.Before;
import org.junit.Test;
import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
import org.onosproject.codec.CodecContext;
import org.onosproject.codec.JsonCodec;
import org.onosproject.codec.impl.CodecManager;
import org.onosproject.core.CoreService;
+import org.onosproject.k8snode.api.DefaultHostNodesInfo;
import org.onosproject.k8snode.api.DefaultK8sApiConfig;
+import org.onosproject.k8snode.api.HostNodesInfo;
import org.onosproject.k8snode.api.K8sApiConfig;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
import static junit.framework.TestCase.assertEquals;
import static org.easymock.EasyMock.createMock;
@@ -51,6 +56,7 @@
MockCodecContext context;
JsonCodec<K8sApiConfig> k8sApiConfigCodec;
+ JsonCodec<HostNodesInfo> hostNodesInfoCodec;
final CoreService mockCoreService = createMock(CoreService.class);
private static final String REST_APP_ID = "org.onosproject.rest";
@@ -62,8 +68,10 @@
public void setUp() {
context = new MockCodecContext();
k8sApiConfigCodec = new K8sApiConfigCodec();
+ hostNodesInfoCodec = new HostNodesInfoCodec();
assertThat(k8sApiConfigCodec, notNullValue());
+ assertThat(hostNodesInfoCodec, notNullValue());
expect(mockCoreService.registerApplication(REST_APP_ID))
.andReturn(APP_ID).anyTimes();
@@ -76,7 +84,16 @@
*/
@Test
public void testK8sApiConfigEncode() {
+ HostNodesInfo info = new DefaultHostNodesInfo.Builder()
+ .hostIp(IpAddress.valueOf("192.168.10.10"))
+ .nodes(ImmutableSet.of("master", "worker"))
+ .build();
+
K8sApiConfig config = DefaultK8sApiConfig.builder()
+ .clusterName("kubernetes")
+ .segmentId(1)
+ .extNetworkCidr(IpPrefix.valueOf("192.168.200.0/24"))
+ .mode(K8sApiConfig.Mode.NORMAL)
.scheme(K8sApiConfig.Scheme.HTTPS)
.ipAddress(IpAddress.valueOf("10.10.10.23"))
.port(6443)
@@ -85,6 +102,7 @@
.caCertData("caCertData")
.clientCertData("clientCertData")
.clientKeyData("clientKeyData")
+ .infos(ImmutableSet.of(info))
.build();
ObjectNode configJson = k8sApiConfigCodec.encode(config, context);
@@ -100,6 +118,10 @@
public void testK8sApiConfigDecode() throws IOException {
K8sApiConfig config = getK8sApiConfig("K8sApiConfig.json");
+ assertEquals("kubernetes", config.clusterName());
+ assertEquals(1, config.segmentId());
+ assertEquals("192.168.200.0/24", config.extNetworkCidr().toString());
+ assertEquals("NORMAL", config.mode().name());
assertEquals("HTTPS", config.scheme().name());
assertEquals("10.134.34.223", config.ipAddress().toString());
assertEquals(6443, config.port());
@@ -107,6 +129,9 @@
assertEquals("caCertData", config.caCertData());
assertEquals("clientCertData", config.clientCertData());
assertEquals("clientKeyData", config.clientKeyData());
+
+ Set<HostNodesInfo> infos = config.infos();
+ assertEquals(1, infos.size());
}
private K8sApiConfig getK8sApiConfig(String resourceName) throws IOException {
@@ -141,6 +166,9 @@
if (entityClass == K8sApiConfig.class) {
return (JsonCodec<T>) k8sApiConfigCodec;
}
+ if (entityClass == HostNodesInfo.class) {
+ return (JsonCodec<T>) hostNodesInfoCodec;
+ }
return manager.getCodec(entityClass);
}
diff --git a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sApiConfigJsonMatcher.java b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sApiConfigJsonMatcher.java
index 2eb42b9..7ec3866 100644
--- a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sApiConfigJsonMatcher.java
+++ b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sApiConfigJsonMatcher.java
@@ -18,6 +18,7 @@
import com.fasterxml.jackson.databind.JsonNode;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.onosproject.k8snode.api.HostNodesInfo;
import org.onosproject.k8snode.api.K8sApiConfig;
/**
@@ -27,6 +28,10 @@
private final K8sApiConfig k8sApiConfig;
+ private static final String CLUSTER_NAME = "clusterName";
+ private static final String SEGMENT_ID = "segmentId";
+ private static final String EXT_NETWORK_CIDR = "extNetworkCidr";
+ private static final String MODE = "mode";
private static final String SCHEME = "scheme";
private static final String IP_ADDRESS = "ipAddress";
private static final String PORT = "port";
@@ -35,6 +40,7 @@
private static final String CA_CERT_DATA = "caCertData";
private static final String CLIENT_CERT_DATA = "clientCertData";
private static final String CLIENT_KEY_DATA = "clientKeyData";
+ private static final String HOST_NODES_INFO = "hostNodesInfo";
private K8sApiConfigJsonMatcher(K8sApiConfig k8sApiConfig) {
this.k8sApiConfig = k8sApiConfig;
@@ -43,6 +49,40 @@
@Override
protected boolean matchesSafely(JsonNode jsonNode, Description description) {
+ // check cluster name
+ String jsonClusterName = jsonNode.get(CLUSTER_NAME).asText();
+ String clusterName = k8sApiConfig.clusterName();
+ if (!jsonClusterName.equals(clusterName)) {
+ description.appendText("cluster name was " + jsonClusterName);
+ return false;
+ }
+
+ // check segment ID
+ int jsonSegmentId = jsonNode.get(SEGMENT_ID).asInt();
+ int segmentId = k8sApiConfig.segmentId();
+ if (jsonSegmentId != segmentId) {
+ description.appendText("Segment ID was " + jsonSegmentId);
+ return false;
+ }
+
+ // check mode
+ String jsonMode = jsonNode.get(MODE).asText();
+ String mode = k8sApiConfig.mode().name();
+ if (!jsonMode.equals(mode)) {
+ description.appendText("mode was " + jsonMode);
+ return false;
+ }
+
+ // check external network CIDR
+ JsonNode jsonCidr = jsonNode.get(EXT_NETWORK_CIDR);
+ String cidr = k8sApiConfig.extNetworkCidr().toString();
+ if (jsonCidr != null) {
+ if (!jsonCidr.asText().equals(cidr)) {
+ description.appendText("External network CIDR was " + jsonCidr);
+ return false;
+ }
+ }
+
// check scheme
String jsonScheme = jsonNode.get(SCHEME).asText();
String scheme = k8sApiConfig.scheme().name();
@@ -119,6 +159,29 @@
}
}
+ // check hostNodesInfo size
+ JsonNode jsonInfos = jsonNode.get(HOST_NODES_INFO);
+ if (jsonInfos.size() != k8sApiConfig.infos().size()) {
+ description.appendText("Info size was " + jsonInfos.size());
+ return false;
+ }
+
+ // check info
+ for (HostNodesInfo info : k8sApiConfig.infos()) {
+ boolean infoFound = false;
+ for (int infoIndex = 0; infoIndex < jsonInfos.size(); infoIndex++) {
+ HostNodesInfoJsonMatcher infoMatcher = HostNodesInfoJsonMatcher.matchesHostNodesInfo(info);
+ if (infoMatcher.matches(jsonInfos.get(infoIndex))) {
+ infoFound = true;
+ break;
+ }
+ }
+ if (!infoFound) {
+ description.appendText("Info not found " + info.toString());
+ return false;
+ }
+ }
+
return true;
}
diff --git a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sHostCodecTest.java b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sHostCodecTest.java
new file mode 100644
index 0000000..c7cb041
--- /dev/null
+++ b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sHostCodecTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2020-present Open Networking Foundation
+ *
+ * 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.k8snode.codec;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.codec.impl.CodecManager;
+import org.onosproject.core.CoreService;
+import org.onosproject.k8snode.api.DefaultK8sHost;
+import org.onosproject.k8snode.api.K8sHost;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onosproject.k8snode.api.K8sHostState.INIT;
+import static org.onosproject.k8snode.codec.K8sHostJsonMatcher.matchesK8sHost;
+import static org.onosproject.net.NetTestTools.APP_ID;
+
+public class K8sHostCodecTest {
+
+ MockCodecContext context;
+
+ JsonCodec<K8sHost> k8sHostCodec;
+
+ final CoreService mockCoreService = createMock(CoreService.class);
+ private static final String REST_APP_ID = "org.onosproject.rest";
+
+ /**
+ * Initial setup for this unit test.
+ */
+ @Before
+ public void setUp() {
+ context = new MockCodecContext();
+ k8sHostCodec = new K8sHostCodec();
+
+ assertThat(k8sHostCodec, notNullValue());
+
+ expect(mockCoreService.registerApplication(REST_APP_ID))
+ .andReturn(APP_ID).anyTimes();
+ replay(mockCoreService);
+ context.registerService(CoreService.class, mockCoreService);
+ }
+
+ /**
+ * Tests the kubernetes host encoding.
+ */
+ @Test
+ public void testK8sHostEncode() {
+ K8sHost host = DefaultK8sHost.builder()
+ .hostIp(IpAddress.valueOf("192.168.200.10"))
+ .state(INIT)
+ .nodeNames(ImmutableSet.of("1", "2"))
+ .build();
+
+ ObjectNode hostJson = k8sHostCodec.encode(host, context);
+ assertThat(hostJson, matchesK8sHost(host));
+ }
+
+ /**
+ * Tests the kubernetes host decoding.
+ */
+ @Test
+ public void testK8sHostDecode() throws IOException {
+ K8sHost host = getK8sHost("K8sHost.json");
+
+ assertEquals("192.168.200.10", host.hostIp().toString());
+ assertEquals("INIT", host.state().name());
+ }
+
+ private K8sHost getK8sHost(String resourceName) throws IOException {
+ InputStream jsonStream = K8sHostCodecTest.class.getResourceAsStream(resourceName);
+ JsonNode json = context.mapper().readTree(jsonStream);
+ assertThat(json, notNullValue());
+ K8sHost host = k8sHostCodec.decode((ObjectNode) json, context);
+ assertThat(host, notNullValue());
+ return host;
+ }
+
+ private class MockCodecContext implements CodecContext {
+ private final ObjectMapper mapper = new ObjectMapper();
+ private final CodecManager manager = new CodecManager();
+ private final Map<Class<?>, Object> services = new HashMap<>();
+
+ /**
+ * Constructs a new mock codec context.
+ */
+ public MockCodecContext() {
+ manager.activate();
+ }
+
+ @Override
+ public ObjectMapper mapper() {
+ return mapper;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> JsonCodec<T> codec(Class<T> entityClass) {
+ if (entityClass == K8sHost.class) {
+ return (JsonCodec<T>) k8sHostCodec;
+ }
+ return manager.getCodec(entityClass);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T getService(Class<T> serviceClass) {
+ return (T) services.get(serviceClass);
+ }
+
+ // for registering mock services
+ public <T> void registerService(Class<T> serviceClass, T impl) {
+ services.put(serviceClass, impl);
+ }
+ }
+}
diff --git a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sHostJsonMatcher.java b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sHostJsonMatcher.java
new file mode 100644
index 0000000..4a0cf73
--- /dev/null
+++ b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sHostJsonMatcher.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2020-present Open Networking Foundation
+ *
+ * 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.k8snode.codec;
+
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.onosproject.k8snode.api.K8sHost;
+
+/**
+ * Hamcrest matcher for kubernetes host.
+ */
+public final class K8sHostJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
+
+ private final K8sHost host;
+
+ private static final String HOST_IP = "hostIp";
+ private static final String NODE_NAMES = "nodeNames";
+ private static final String STATE = "state";
+
+ private K8sHostJsonMatcher(K8sHost host) {
+ this.host = host;
+ }
+
+ @Override
+ protected boolean matchesSafely(JsonNode jsonNode, Description description) {
+
+ // check host IP
+ String jsonHostIp = jsonNode.get(HOST_IP).asText();
+ String hostIp = host.hostIp().toString();
+ if (!jsonHostIp.equals(hostIp)) {
+ description.appendText("host IP was " + jsonHostIp);
+ return false;
+ }
+
+ // check state
+ String jsonState = jsonNode.get(STATE).asText();
+ String state = host.state().name();
+ if (!jsonState.equals(state)) {
+ description.appendText("state was " + jsonState);
+ return false;
+ }
+
+ // check node names size
+ JsonNode jsonNames = jsonNode.get(NODE_NAMES);
+ if (jsonNames.size() != host.nodeNames().size()) {
+ description.appendText("Node names size was " + jsonNames.size());
+ return false;
+ }
+
+ // check node names
+ for (String name : host.nodeNames()) {
+ boolean nameFound = false;
+ for (int nameIndex = 0; nameIndex < jsonNames.size(); nameIndex++) {
+ if (name.equals(jsonNames.get(nameIndex).asText())) {
+ nameFound = true;
+ break;
+ }
+ }
+
+ if (!nameFound) {
+ description.appendText("Name not found " + name);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(host.toString());
+ }
+
+ /**
+ * Factory to allocate an k8s host matcher.
+ *
+ * @param host k8s host object we are looking for
+ * @return matcher
+ */
+ public static K8sHostJsonMatcher matchesK8sHost(K8sHost host) {
+ return new K8sHostJsonMatcher(host);
+ }
+}
diff --git a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sNodeCodecTest.java b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sNodeCodecTest.java
index 556e462..c6861be 100644
--- a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sNodeCodecTest.java
+++ b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sNodeCodecTest.java
@@ -78,8 +78,10 @@
@Test
public void testK8sMinionNodeEncode() {
K8sNode node = DefaultK8sNode.builder()
+ .clusterName("kubernetes")
.hostname("minion")
.type(K8sNode.Type.MINION)
+ .segmentId(100)
.state(K8sNodeState.INIT)
.managementIp(IpAddress.valueOf("10.10.10.1"))
.dataIp(IpAddress.valueOf("20.20.20.2"))
@@ -102,8 +104,10 @@
public void testK8sMinionNodeDecode() throws IOException {
K8sNode node = getK8sNode("K8sMinionNode.json");
+ assertEquals("kubernetes", node.clusterName());
assertEquals("minion", node.hostname());
assertEquals("MINION", node.type().name());
+ assertEquals(100, node.segmentId());
assertEquals("172.16.130.4", node.managementIp().toString());
assertEquals("172.16.130.4", node.dataIp().toString());
assertEquals("of:00000000000000a1", node.intgBridge().toString());
diff --git a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sNodeJsonMatcher.java b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sNodeJsonMatcher.java
index b953f54..816ca76 100644
--- a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sNodeJsonMatcher.java
+++ b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sNodeJsonMatcher.java
@@ -28,8 +28,10 @@
private final K8sNode node;
+ private static final String CLUSTER_NAME = "clusterName";
private static final String HOSTNAME = "hostname";
private static final String TYPE = "type";
+ private static final String SEGMENT_ID = "segmentId";
private static final String MANAGEMENT_IP = "managementIp";
private static final String DATA_IP = "dataIp";
private static final String INTEGRATION_BRIDGE = "integrationBridge";
@@ -45,6 +47,14 @@
@Override
protected boolean matchesSafely(JsonNode jsonNode, Description description) {
+ // check cluster name
+ String jsonClusterName = jsonNode.get(CLUSTER_NAME).asText();
+ String clusterName = node.clusterName();
+ if (!jsonClusterName.equals(clusterName)) {
+ description.appendText("cluster name was " + jsonClusterName);
+ return false;
+ }
+
// check hostname
String jsonHostname = jsonNode.get(HOSTNAME).asText();
String hostname = node.hostname();
@@ -61,6 +71,16 @@
return false;
}
+ // check segment ID
+ JsonNode jsonSegmentId = jsonNode.get(SEGMENT_ID);
+ if (jsonSegmentId != null) {
+ int segmentId = jsonSegmentId.asInt();
+ if (segmentId != node.segmentId()) {
+ description.appendText("segment ID was " + segmentId);
+ return false;
+ }
+ }
+
// check management IP
String jsonMgmtIp = jsonNode.get(MANAGEMENT_IP).asText();
String mgmtIp = node.managementIp().toString();
diff --git a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/impl/ExternalNetworkManagerTest.java b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/impl/ExternalNetworkManagerTest.java
new file mode 100644
index 0000000..f236491
--- /dev/null
+++ b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/impl/ExternalNetworkManagerTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2020-present Open Networking Foundation
+ *
+ * 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.k8snode.impl;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.store.service.TestStorageService;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for external network manager.
+ */
+public class ExternalNetworkManagerTest {
+
+ private static final ApplicationId TEST_APP_ID = new DefaultApplicationId(1, "test");
+
+ private ExternalNetworkManager target;
+
+ /**
+ * Initial setup for this unit test.
+ */
+ @Before
+ public void setUp() {
+ target = new ExternalNetworkManager();
+
+ target.coreService = new TestCoreService();
+ target.storageService = new TestStorageService();
+ target.activate();
+ }
+
+ /**
+ * Clean up unit test.
+ */
+ @After
+ public void tearDown() {
+ target.deactivate();
+ target = null;
+ }
+
+ /**
+ * Checks if creating and removing a config work well with proper events.
+ */
+ @Test
+ public void testObtainGatewayIp() {
+ IpPrefix cidr = IpPrefix.valueOf("192.168.200.0/24");
+ target.registerNetwork(cidr);
+
+ assertEquals(target.getGatewayIp(cidr), IpAddress.valueOf("192.168.200.1"));
+ }
+
+ @Test
+ public void testAllocateReleaseIp() {
+ IpPrefix cidr = IpPrefix.valueOf("192.168.200.0/24");
+ target.registerNetwork(cidr);
+ IpAddress ip = target.allocateIp(cidr);
+ assertEquals(251, target.getAllIps(cidr).size());
+
+ target.releaseIp(cidr, ip);
+ assertEquals(252, target.getAllIps(cidr).size());
+ }
+
+ private static class TestCoreService extends CoreServiceAdapter {
+ @Override
+ public ApplicationId registerApplication(String name) {
+ return TEST_APP_ID;
+ }
+ }
+}
diff --git a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/impl/K8sApiConfigManagerTest.java b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/impl/K8sApiConfigManagerTest.java
index f46feb8..badfced 100644
--- a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/impl/K8sApiConfigManagerTest.java
+++ b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/impl/K8sApiConfigManagerTest.java
@@ -72,12 +72,18 @@
public void setUp() {
apiConfig1 = DefaultK8sApiConfig.builder()
+ .clusterName("kubernetes1")
+ .segmentId(1)
+ .mode(K8sApiConfig.Mode.NORMAL)
.scheme(K8sApiConfig.Scheme.HTTP)
.ipAddress(IpAddress.valueOf("10.10.10.2"))
.port(6443)
.state(DISCONNECTED)
.build();
apiConfig2 = DefaultK8sApiConfig.builder()
+ .clusterName("kubernetes2")
+ .segmentId(2)
+ .mode(K8sApiConfig.Mode.NORMAL)
.scheme(K8sApiConfig.Scheme.HTTPS)
.ipAddress(IpAddress.valueOf("10.10.10.3"))
.port(6443)
@@ -88,6 +94,9 @@
.clientKeyData("clientKeyData")
.build();
apiConfig3 = DefaultK8sApiConfig.builder()
+ .clusterName("kubernetes3")
+ .segmentId(3)
+ .mode(K8sApiConfig.Mode.PASSTHROUGH)
.scheme(K8sApiConfig.Scheme.HTTP)
.ipAddress(IpAddress.valueOf("10.10.10.4"))
.port(8080)
diff --git a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/impl/K8sHostManagerTest.java b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/impl/K8sHostManagerTest.java
new file mode 100644
index 0000000..3374672
--- /dev/null
+++ b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/impl/K8sHostManagerTest.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2020-present Open Networking Foundation
+ *
+ * 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.k8snode.impl;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.MoreExecutors;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cluster.ClusterServiceAdapter;
+import org.onosproject.cluster.LeadershipServiceAdapter;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.event.Event;
+import org.onosproject.k8snode.api.DefaultK8sHost;
+import org.onosproject.k8snode.api.K8sHost;
+import org.onosproject.k8snode.api.K8sHostEvent;
+import org.onosproject.k8snode.api.K8sHostListener;
+import org.onosproject.k8snode.api.K8sHostState;
+import org.onosproject.store.service.TestStorageService;
+
+import java.util.List;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.k8snode.api.K8sHostEvent.Type.K8S_HOST_COMPLETE;
+import static org.onosproject.k8snode.api.K8sHostEvent.Type.K8S_HOST_CREATED;
+import static org.onosproject.k8snode.api.K8sHostEvent.Type.K8S_HOST_REMOVED;
+import static org.onosproject.k8snode.api.K8sHostEvent.Type.K8S_HOST_UPDATED;
+import static org.onosproject.k8snode.api.K8sHostEvent.Type.K8S_NODES_ADDED;
+import static org.onosproject.k8snode.api.K8sHostEvent.Type.K8S_NODES_REMOVED;
+import static org.onosproject.k8snode.api.K8sHostState.COMPLETE;
+import static org.onosproject.k8snode.api.K8sHostState.INIT;
+
+/**
+ * Unit tests for kubernetes host manager.
+ */
+public class K8sHostManagerTest {
+
+ private static final ApplicationId TEST_APP_ID = new DefaultApplicationId(1, "test");
+
+ private static final String ERR_SIZE = "Number of hosts did not match";
+ private static final String ERR_NOT_MATCH = "Host did not match";
+ private static final String ERR_NOT_FOUND = "Host did not exist";
+
+ private static final IpAddress HOST_IP_1 = IpAddress.valueOf("192.168.100.2");
+ private static final IpAddress HOST_IP_2 = IpAddress.valueOf("192.168.101.2");
+ private static final IpAddress HOST_IP_3 = IpAddress.valueOf("192.168.102.2");
+
+ private static final K8sHost HOST_1 = createHost(
+ HOST_IP_1,
+ INIT,
+ ImmutableSet.of("1", "2")
+ );
+
+ private static final K8sHost HOST_2 = createHost(
+ HOST_IP_2,
+ INIT,
+ ImmutableSet.of("3", "4")
+ );
+
+ private static final K8sHost HOST_3 = createHost(
+ HOST_IP_3,
+ COMPLETE,
+ ImmutableSet.of("5", "6")
+ );
+
+ private final TestK8sHostListener testListener = new TestK8sHostListener();
+
+ private K8sHostManager target;
+ private DistributedK8sHostStore hostStore;
+
+ /**
+ * Initial setup for this unit test.
+ */
+ @Before
+ public void setUp() {
+ hostStore = new DistributedK8sHostStore();
+ TestUtils.setField(hostStore, "coreService", new TestCoreService());
+ TestUtils.setField(hostStore, "storageService", new TestStorageService());
+ TestUtils.setField(hostStore, "eventExecutor", MoreExecutors.newDirectExecutorService());
+ hostStore.activate();
+
+ hostStore.createHost(HOST_2);
+ hostStore.createHost(HOST_3);
+
+ target = new K8sHostManager();
+ target.storageService = new TestStorageService();
+ target.coreService = new TestCoreService();
+ target.clusterService = new TestClusterService();
+ target.leadershipService = new TestLeadershipService();
+ target.hostStore = hostStore;
+ target.addListener(testListener);
+ target.activate();
+ testListener.events.clear();
+ }
+
+ /**
+ * Clean up unit test.
+ */
+ @After
+ public void tearDown() {
+ target.removeListener(testListener);
+ target.deactivate();
+ hostStore.deactivate();
+ hostStore = null;
+ target = null;
+ }
+
+ /**
+ * Checks if creating and removing a host work well with proper events.
+ */
+ @Test
+ public void testCreateAndRemoveHost() {
+ target.createHost(HOST_1);
+ assertEquals(ERR_SIZE, 3, target.hosts().size());
+ assertNotNull(target.host(HOST_IP_1));
+
+ target.removeHost(HOST_IP_1);
+ assertEquals(ERR_SIZE, 2, target.hosts().size());
+ assertNull(target.host(HOST_IP_1));
+
+ validateEvents(K8S_HOST_CREATED, K8S_HOST_REMOVED);
+ }
+
+ /**
+ * Checks if creating null host fails with proper exception.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testCreateNullHost() {
+ target.createHost(null);
+ }
+
+ /**
+ * Checks if creating a duplicated host fails with proper exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateDuplicateHost() {
+ target.createHost(HOST_1);
+ target.createHost(HOST_1);
+ }
+
+ /**
+ * Checks if removing null host fails with proper exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testRemoveNullHost() {
+ target.removeHost(null);
+ }
+
+ /**
+ * Checks if updating a host works well with proper event.
+ */
+ @Test
+ public void testUpdateHost() {
+ K8sHost updated = HOST_2.updateState(COMPLETE);
+ target.updateHost(updated);
+ validateEvents(K8S_HOST_UPDATED, K8S_HOST_COMPLETE);
+ }
+
+ /**
+ * Checks if updating a null host fails with proper exception.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testUpdateNullHost() {
+ target.updateHost(null);
+ }
+
+ /**
+ * Checks if updating not existing host fails with proper exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testUpdateNotExistingHost() {
+ target.updateHost(HOST_1);
+ }
+
+ /**
+ * Checks if adding nodes into host works well with proper event.
+ */
+ @Test
+ public void testAddNodesToHost() {
+ K8sHost updated = HOST_2.updateNodeNames(ImmutableSet.of("3", "4", "5"));
+ target.updateHost(updated);
+ validateEvents(K8S_HOST_UPDATED, K8S_NODES_ADDED);
+ }
+
+ /**
+ * Checks if removing nodes from host works well with proper event.
+ */
+ @Test
+ public void testRemoveNodesFromHost() {
+ K8sHost updated = HOST_2.updateNodeNames(ImmutableSet.of("3"));
+ target.updateHost(updated);
+ validateEvents(K8S_HOST_UPDATED, K8S_NODES_REMOVED);
+ }
+
+ /**
+ * Checks if getting all hosts method returns correct set of nodes.
+ */
+ @Test
+ public void testGetAllHosts() {
+ assertEquals(ERR_SIZE, 2, target.hosts().size());
+ assertTrue(ERR_NOT_FOUND, target.hosts().contains(HOST_2));
+ assertTrue(ERR_NOT_FOUND, target.hosts().contains(HOST_3));
+ }
+
+ /**
+ * Checks if getting complete hosts method returns correct set of nodes.
+ */
+ @Test
+ public void testGetCompleteHosts() {
+ assertEquals(ERR_SIZE, 1, target.completeHosts().size());
+ assertTrue(ERR_NOT_FOUND, target.completeHosts().contains(HOST_3));
+ }
+
+ private void validateEvents(Enum... types) {
+ int i = 0;
+ assertEquals("Number of events did not match", types.length, testListener.events.size());
+ for (Event event : testListener.events) {
+ assertEquals("Incorrect event received", types[i], event.type());
+ i++;
+ }
+ testListener.events.clear();
+ }
+
+ private static class TestK8sHostListener implements K8sHostListener {
+ private List<K8sHostEvent> events = Lists.newArrayList();
+
+ @Override
+ public void event(K8sHostEvent event) {
+ events.add(event);
+ }
+ }
+
+ private static class TestCoreService extends CoreServiceAdapter {
+ @Override
+ public ApplicationId registerApplication(String name) {
+ return TEST_APP_ID;
+ }
+ }
+
+ private class TestClusterService extends ClusterServiceAdapter {
+
+ }
+
+ private static class TestLeadershipService extends LeadershipServiceAdapter {
+
+ }
+
+ private static K8sHost createHost(IpAddress hostIp, K8sHostState state, Set<String> nodeNames) {
+ return DefaultK8sHost.builder()
+ .hostIp(hostIp)
+ .nodeNames(nodeNames)
+ .state(state)
+ .build();
+ }
+}
diff --git a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/impl/K8sNodeManagerTest.java b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/impl/K8sNodeManagerTest.java
index 50d769f..0bb0258 100644
--- a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/impl/K8sNodeManagerTest.java
+++ b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/impl/K8sNodeManagerTest.java
@@ -68,6 +68,8 @@
private static final String ERR_NOT_MATCH = "Node did not match";
private static final String ERR_NOT_FOUND = "Node did not exist";
+ private static final String CLUSTER_NAME = "kubernetes";
+
private static final String MINION_1_HOSTNAME = "minion_1";
private static final String MINION_2_HOSTNAME = "minion_2";
private static final String MINION_3_HOSTNAME = "minion_3";
@@ -84,31 +86,41 @@
private static final Device MINION_2_LOCAL_DEVICE = createDevice(8);
private static final Device MINION_3_LOCAL_DEVICE = createDevice(9);
+ private static final Device MINION_1_TUN_DEVICE = createDevice(10);
+ private static final Device MINION_2_TUN_DEVICE = createDevice(11);
+ private static final Device MINION_3_TUN_DEVICE = createDevice(12);
+
private static final K8sNode MINION_1 = createNode(
+ CLUSTER_NAME,
MINION_1_HOSTNAME,
MINION,
MINION_1_INTG_DEVICE,
MINION_1_EXT_DEVICE,
MINION_1_LOCAL_DEVICE,
+ MINION_1_TUN_DEVICE,
IpAddress.valueOf("10.100.0.1"),
INIT
);
private static final K8sNode MINION_2 = createNode(
+ CLUSTER_NAME,
MINION_2_HOSTNAME,
MINION,
MINION_2_INTG_DEVICE,
MINION_2_EXT_DEVICE,
MINION_2_LOCAL_DEVICE,
+ MINION_2_TUN_DEVICE,
IpAddress.valueOf("10.100.0.2"),
INIT
);
private static final K8sNode MINION_3 = createNode(
+ CLUSTER_NAME,
MINION_3_HOSTNAME,
MINION,
MINION_3_INTG_DEVICE,
MINION_3_EXT_DEVICE,
MINION_3_LOCAL_DEVICE,
+ MINION_3_TUN_DEVICE,
IpAddress.valueOf("10.100.0.3"),
COMPLETE
);
@@ -334,16 +346,18 @@
}
- private static K8sNode createNode(String hostname, K8sNode.Type type,
+ private static K8sNode createNode(String clusterName, String hostname, K8sNode.Type type,
Device intgBridge, Device extBridge,
- Device localBridge, IpAddress ipAddr,
- K8sNodeState state) {
+ Device localBridge, Device tunBridge,
+ IpAddress ipAddr, K8sNodeState state) {
return DefaultK8sNode.builder()
.hostname(hostname)
+ .clusterName(clusterName)
.type(type)
.intgBridge(intgBridge.id())
.extBridge(extBridge.id())
.localBridge(localBridge.id())
+ .tunBridge(tunBridge.id())
.managementIp(ipAddr)
.dataIp(ipAddr)
.state(state)
diff --git a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/web/K8sNodeWebResourceTest.java b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/web/K8sNodeWebResourceTest.java
index 207bd0e..1005ab2 100644
--- a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/web/K8sNodeWebResourceTest.java
+++ b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/web/K8sNodeWebResourceTest.java
@@ -25,11 +25,13 @@
import org.onosproject.codec.impl.CodecManager;
import org.onosproject.k8snode.api.DefaultK8sApiConfig;
import org.onosproject.k8snode.api.DefaultK8sNode;
+import org.onosproject.k8snode.api.HostNodesInfo;
import org.onosproject.k8snode.api.K8sApiConfig;
import org.onosproject.k8snode.api.K8sApiConfigAdminService;
import org.onosproject.k8snode.api.K8sNode;
import org.onosproject.k8snode.api.K8sNodeAdminService;
import org.onosproject.k8snode.api.K8sNodeState;
+import org.onosproject.k8snode.codec.HostNodesInfoCodec;
import org.onosproject.k8snode.codec.K8sApiConfigCodec;
import org.onosproject.k8snode.codec.K8sNodeCodec;
import org.onosproject.net.DeviceId;
@@ -81,6 +83,7 @@
codecService.activate();
codecService.registerCodec(K8sNode.class, new K8sNodeCodec());
codecService.registerCodec(K8sApiConfig.class, new K8sApiConfigCodec());
+ codecService.registerCodec(HostNodesInfo.class, new HostNodesInfoCodec());
ServiceDirectory testDirectory =
new TestServiceDirectory()
.add(K8sNodeAdminService.class, mockK8sNodeAdminService)
@@ -89,6 +92,7 @@
setServiceDirectory(testDirectory);
k8sNode = DefaultK8sNode.builder()
+ .clusterName("kubernetes")
.hostname("minion-node")
.type(K8sNode.Type.MINION)
.dataIp(IpAddress.valueOf("10.134.34.222"))
@@ -99,6 +103,9 @@
.build();
k8sApiConfig = DefaultK8sApiConfig.builder()
+ .clusterName("kubernetes")
+ .segmentId(1)
+ .mode(K8sApiConfig.Mode.NORMAL)
.scheme(K8sApiConfig.Scheme.HTTPS)
.ipAddress(IpAddress.valueOf("10.134.34.223"))
.port(6443)
diff --git a/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/codec/K8sApiConfig.json b/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/codec/K8sApiConfig.json
index f706cc3..70639b9 100644
--- a/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/codec/K8sApiConfig.json
+++ b/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/codec/K8sApiConfig.json
@@ -1,9 +1,21 @@
{
+ "clusterName": "kubernetes",
+ "segmentId": 1,
+ "extNetworkCidr": "192.168.200.0/24",
+ "mode": "NORMAL",
"scheme" : "HTTPS",
"ipAddress" : "10.134.34.223",
"port" : 6443,
"token": "token",
"caCertData": "caCertData",
"clientCertData": "clientCertData",
- "clientKeyData": "clientKeyData"
+ "clientKeyData": "clientKeyData",
+ "hostNodesInfo": [
+ {
+ "hostIp": "192.168.10.10",
+ "nodes": [
+ "master", "worker"
+ ]
+ }
+ ]
}
\ No newline at end of file
diff --git a/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/codec/K8sHost.json b/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/codec/K8sHost.json
new file mode 100644
index 0000000..4abcf2f
--- /dev/null
+++ b/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/codec/K8sHost.json
@@ -0,0 +1,7 @@
+{
+ "hostIp": "192.168.200.10",
+ "state": "INIT",
+ "nodeNames": [
+ "1", "2"
+ ]
+}
\ No newline at end of file
diff --git a/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/codec/K8sMinionNode.json b/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/codec/K8sMinionNode.json
index 2930bfd..495dfee 100644
--- a/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/codec/K8sMinionNode.json
+++ b/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/codec/K8sMinionNode.json
@@ -1,6 +1,8 @@
{
+ "clusterName": "kubernetes",
"hostname": "minion",
"type": "MINION",
+ "segmentId": 100,
"managementIp": "172.16.130.4",
"dataIp": "172.16.130.4",
"integrationBridge": "of:00000000000000a1",
diff --git a/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/web/k8s-api-config.json b/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/web/k8s-api-config.json
index 958d434..d29bcee 100644
--- a/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/web/k8s-api-config.json
+++ b/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/web/k8s-api-config.json
@@ -1,13 +1,24 @@
{
"apiConfigs" : [
{
+ "clusterName": "kubernetes",
+ "segmentId": 1,
+ "mode": "NORMAL",
"scheme" : "HTTPS",
"ipAddress" : "10.134.34.223",
"port" : 6443,
"token": "token",
"caCertData": "caCertData",
"clientCertData": "clientCertData",
- "clientKeyData": "clientKeyData"
+ "clientKeyData": "clientKeyData",
+ "hostNodesInfo": [
+ {
+ "hostIp": "192.168.10.10",
+ "nodes": [
+ "master", "worker"
+ ]
+ }
+ ]
}
]
}
\ No newline at end of file
diff --git a/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/web/k8s-node-minion-config.json b/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/web/k8s-node-minion-config.json
index 8c65bb5..1e7d4a4 100644
--- a/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/web/k8s-node-minion-config.json
+++ b/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/web/k8s-node-minion-config.json
@@ -1,6 +1,7 @@
{
"nodes" : [
{
+ "clusterName": "kubernetes",
"hostname" : "minion-node",
"type" : "MINION",
"managementIp" : "10.134.231.32",