blob: 84ae19b10fb30339954ef02a74c526faa49354d2 [file] [log] [blame]
/*
* Copyright 2017-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.netconf.client.impl;
import com.google.common.annotations.Beta;
import org.onosproject.cluster.NodeId;
import org.onosproject.net.DeviceId;
import org.onosproject.netconf.NetconfController;
import org.onosproject.netconf.NetconfDevice;
import org.onosproject.netconf.NetconfException;
import org.onosproject.netconf.NetconfSession;
import org.onosproject.netconf.client.NetconfTranslator;
import org.onosproject.yang.model.DataNode;
import org.onosproject.yang.model.DefaultResourceData;
import org.onosproject.yang.model.InnerNode;
import org.onosproject.yang.model.KeyLeaf;
import org.onosproject.yang.model.LeafListKey;
import org.onosproject.yang.model.LeafNode;
import org.onosproject.yang.model.ListKey;
import org.onosproject.yang.model.NodeKey;
import org.onosproject.yang.model.ResourceData;
import org.onosproject.yang.model.ResourceId;
import org.onosproject.yang.model.SchemaContext;
import org.onosproject.yang.model.SchemaContextProvider;
import org.onosproject.yang.model.SchemaId;
import org.onosproject.yang.runtime.CompositeStream;
import org.onosproject.yang.runtime.DefaultAnnotatedNodeInfo;
import org.onosproject.yang.runtime.DefaultAnnotation;
import org.onosproject.yang.runtime.DefaultCompositeData;
import org.onosproject.yang.runtime.DefaultCompositeStream;
import org.onosproject.yang.runtime.DefaultRuntimeContext;
import org.onosproject.yang.runtime.DefaultYangSerializerContext;
import org.onosproject.yang.runtime.SerializerHelper;
import org.onosproject.yang.runtime.YangRuntimeService;
import org.onosproject.yang.runtime.YangSerializerContext;
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.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onosproject.netconf.DatastoreId.RUNNING;
import static org.onosproject.yang.model.DataNode.Type.SINGLE_INSTANCE_LEAF_VALUE_NODE;
import static org.onosproject.yang.runtime.SerializerHelper.addDataNode;
/*FIXME these imports are not visible using OSGI*/
/*FIXME these imports are not visible using OSGI*/
/*TODO once the API's are finalized this comment will be made more specified.*/
/**
* Translator which accepts data types defined for the DynamicConfigService and
* makes the appropriate calls to NETCONF devices before encoding and returning
* responses in formats suitable for the DynamicConfigService.
* <p>
* NOTE: This entity does not ensure you are the master of a device you attempt
* to contact. If you are not the master an error will be thrown because there
* will be no session available.
*/
@Beta
@Component(immediate = true, service = NetconfTranslator.class)
public class NetconfTranslatorImpl implements NetconfTranslator {
private static final Logger log = LoggerFactory
.getLogger(NetconfTranslator.class);
private NodeId localNodeId;
private static final String GET_CONFIG_MESSAGE_REGEX =
"<data>\n?\\s*(.*?)\n?\\s*</data>";
private static final int GET_CONFIG_CORE_MESSAGE_GROUP = 1;
private static final Pattern GET_CONFIG_CORE_MESSAGE_PATTERN =
Pattern.compile(GET_CONFIG_MESSAGE_REGEX, Pattern.DOTALL);
private static final String GET_CORE_MESSAGE_REGEX = "<data>\n?\\s*(.*?)\n?\\s*</data>";
private static final int GET_CORE_MESSAGE_GROUP = 1;
private static final Pattern GET_CORE_MESSAGE_PATTERN =
Pattern.compile(GET_CORE_MESSAGE_REGEX, Pattern.DOTALL);
private static final String NETCONF_1_0_BASE_NAMESPACE =
"urn:ietf:params:xml:ns:netconf:base:1.0";
private static final String GET_URI = "urn:ietf:params:xml:ns:yang:" +
"yrt-ietf-network:networks/network/node";
private static final String XML_ENCODING_SPECIFIER = "xml";
private static final String OP_SPECIFIER = "xc:operation";
private static final String REPLACE_OP_SPECIFIER = "replace";
private static final String DELETE_OP_SPECIFIER = "delete";
private static final String XMLNS_XC_SPECIFIER = "xmlns:xc";
private static final String XMLNS_SPECIFIER = "xmlns";
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected NetconfController netconfController;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected YangRuntimeService yangRuntimeService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected SchemaContextProvider schemaContextProvider;
@Activate
public void activate(ComponentContext context) {
log.info("Started");
}
@Deactivate
public void deactivate() {
log.info("Stopped");
}
@Override
public ResourceData getDeviceConfig(DeviceId deviceId) throws IOException {
NetconfSession session = getNetconfSession(deviceId);
/*FIXME "running" will be replaced with an enum once netconf supports multiple datastores.*/
String reply = session.getConfig(RUNNING);
Matcher protocolStripper = GET_CONFIG_CORE_MESSAGE_PATTERN.matcher(reply);
reply = protocolStripper.group(GET_CONFIG_CORE_MESSAGE_GROUP);
return yangRuntimeService.decode(
new DefaultCompositeStream(
null,
/*FIXME is UTF_8 the appropriate encoding? */
new ByteArrayInputStream(reply.getBytes(StandardCharsets.UTF_8))),
new DefaultRuntimeContext.Builder()
.setDataFormat(XML_ENCODING_SPECIFIER)
.addAnnotation(
new DefaultAnnotation(XMLNS_SPECIFIER,
NETCONF_1_0_BASE_NAMESPACE))
.build()).resourceData();
}
@Override
public boolean editDeviceConfig(DeviceId deviceId, ResourceData resourceData,
NetconfTranslator.OperationType operationType) throws IOException {
NetconfSession session = getNetconfSession(deviceId);
SchemaContext context = schemaContextProvider
.getSchemaContext(ResourceId.builder().addBranchPointSchema("/", null).build());
ResourceData modifiedPathResourceData = getResourceData(resourceData.resourceId(),
resourceData.dataNodes(),
new DefaultYangSerializerContext(context, null));
DefaultCompositeData.Builder compositeDataBuilder = DefaultCompositeData
.builder()
.resourceData(modifiedPathResourceData);
for (DataNode node : resourceData.dataNodes()) {
ResourceId resourceId = resourceData.resourceId();
if (operationType != OperationType.DELETE) {
resourceId = getAnnotatedNodeResourceId(
resourceData.resourceId(), node);
}
if (resourceId != null) {
DefaultAnnotatedNodeInfo.Builder annotatedNodeInfo =
DefaultAnnotatedNodeInfo.builder();
annotatedNodeInfo.resourceId(resourceId);
annotatedNodeInfo.addAnnotation(
new DefaultAnnotation(
OP_SPECIFIER, operationType == OperationType.DELETE ?
DELETE_OP_SPECIFIER : REPLACE_OP_SPECIFIER));
compositeDataBuilder.addAnnotatedNodeInfo(annotatedNodeInfo.build());
}
}
CompositeStream config = yangRuntimeService.encode(
compositeDataBuilder.build(),
new DefaultRuntimeContext.Builder()
.setDataFormat(XML_ENCODING_SPECIFIER)
.addAnnotation(new DefaultAnnotation(
XMLNS_XC_SPECIFIER, NETCONF_1_0_BASE_NAMESPACE))
.build());
/* FIXME need to fix to string conversion. */
try {
String reply = session.requestSync(Utils.editConfig(streamToString(
config.resourceData())));
} catch (NetconfException e) {
log.error("failed to send a request sync", e);
return false;
}
/* NOTE: a failure to edit is reflected as a NetconfException.*/
return true;
}
@Override
public ResourceData getDeviceState(DeviceId deviceId) throws IOException {
NetconfSession session = getNetconfSession(deviceId);
/*TODO the first parameter will come into use if get is required to support filters.*/
String reply = session.get(null, null);
Matcher protocolStripper = GET_CORE_MESSAGE_PATTERN.matcher(reply);
reply = protocolStripper.group(GET_CORE_MESSAGE_GROUP);
return yangRuntimeService.decode(
new DefaultCompositeStream(
null,
/*FIXME is UTF_8 the appropriate encoding? */
new ByteArrayInputStream(reply.getBytes(StandardCharsets.UTF_8))),
new DefaultRuntimeContext.Builder()
.setDataFormat(XML_ENCODING_SPECIFIER)
.addAnnotation(
new DefaultAnnotation(
XMLNS_SPECIFIER,
NETCONF_1_0_BASE_NAMESPACE))
.build()).resourceData();
/* NOTE: a failure to get is reflected as a NetconfException.*/
}
/**
* Returns a session for the specified deviceId if this node is its master,
* returns null otherwise.
*
* @param deviceId the id of node for which we wish to retrieve a session
* @return a NetconfSession with the specified node or null
*/
private NetconfSession getNetconfSession(DeviceId deviceId) {
NetconfDevice device = netconfController.getNetconfDevice(deviceId);
checkNotNull(device, "The specified deviceId could not be found by the NETCONF controller.");
NetconfSession session = device.getSession();
checkNotNull(session, "A session could not be retrieved for the specified deviceId.");
return session;
}
/**
* Accepts a stream and converts it to a string.
*
* @param stream the stream to be converted
* @return a string with the same sequence of characters as the stream
* @throws IOException if reading from the stream fails
*/
private String streamToString(InputStream stream) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
StringBuilder builder = new StringBuilder();
String nextLine = reader.readLine();
while (nextLine != null) {
builder.append(nextLine);
nextLine = reader.readLine();
}
return builder.toString();
}
/**
* Returns resource data having resource id as "/" and data node tree
* starting from "/" by creating data nodes for given resource id(parent
* node for given list of nodes) and list of child nodes.
* <p>
* This api will be used in encode flow only.
*
* @param rid resource identifier till parent node
* @param nodes list of data nodes
* @param cont yang serializer context
* @return resource data.
*/
public static ResourceData getResourceData(
ResourceId rid, List<DataNode> nodes, YangSerializerContext cont) {
if (rid == null) {
ResourceData.Builder resData = DefaultResourceData.builder();
for (DataNode node : nodes) {
resData.addDataNode(node);
}
return resData.build();
}
List<NodeKey> keys = rid.nodeKeys();
Iterator<NodeKey> it = keys.iterator();
DataNode.Builder dbr = SerializerHelper.initializeDataNode(cont);
// checking the resource id weather it is getting started from / or not
while (it.hasNext()) {
NodeKey nodekey = it.next();
SchemaId sid = nodekey.schemaId();
dbr = addDataNode(dbr, sid.name(), sid.namespace(),
null, null);
if (nodekey instanceof ListKey) {
for (KeyLeaf keyLeaf : ((ListKey) nodekey).keyLeafs()) {
String val;
if (keyLeaf.leafValue() == null) {
val = null;
} else {
val = keyLeaf.leafValAsString();
}
dbr = addDataNode(dbr, keyLeaf.leafSchema().name(),
sid.namespace(), val,
SINGLE_INSTANCE_LEAF_VALUE_NODE);
}
}
}
if (dbr instanceof LeafNode.Builder &&
(nodes != null && !nodes.isEmpty())) {
//exception "leaf/leaf-list can not have child node"
}
if (nodes != null && !nodes.isEmpty()) {
// adding the parent node for given list of nodes
for (DataNode node : nodes) {
dbr = ((InnerNode.Builder) dbr).addNode(node);
}
}
/*FIXME this can be uncommented for use with versions of onos-yang-tools newer than 1.12.0-b6*/
// while (dbr.parent() != null) {
// dbr = SerializerHelper.exitDataNode(dbr);
// }
ResourceData.Builder resData = DefaultResourceData.builder();
resData.addDataNode(dbr.build());
resData.resourceId(null);
return resData.build();
}
/**
* Returns resource id for annotated data node by adding resource id of top
* level data node to given resource id.
* <p>
* Annotation will be added to node based on the updated resource id.
* This api will be used in encode flow only.
*
* @param rid resource identifier till parent node
* @param node data node
* @return updated resource id.
*/
public static ResourceId getAnnotatedNodeResourceId(ResourceId rid,
DataNode node) {
String val;
ResourceId.Builder rIdBldr = ResourceId.builder();
if (rid != null) {
try {
rIdBldr = rid.copyBuilder();
} catch (CloneNotSupportedException e) {
log.debug("clone not supported", e);
}
} else {
rIdBldr.addBranchPointSchema("/", null);
}
DataNode.Type type = node.type();
NodeKey k = node.key();
SchemaId sid = k.schemaId();
switch (type) {
case MULTI_INSTANCE_LEAF_VALUE_NODE:
val = ((LeafListKey) k).value().toString();
rIdBldr.addLeafListBranchPoint(sid.name(), sid.namespace(), val);
break;
case MULTI_INSTANCE_NODE:
rIdBldr.addBranchPointSchema(sid.name(), sid.namespace());
// Preparing the list of key values for multiInstanceNode
for (KeyLeaf keyLeaf : ((ListKey) node.key()).keyLeafs()) {
val = keyLeaf.leafValAsString();
rIdBldr.addKeyLeaf(keyLeaf.leafSchema().name(), sid.namespace(), val);
}
break;
case SINGLE_INSTANCE_LEAF_VALUE_NODE:
case SINGLE_INSTANCE_NODE:
rIdBldr.addBranchPointSchema(sid.name(), sid.namespace());
break;
default:
throw new IllegalArgumentException();
}
return rIdBldr.build();
}
}