blob: 359b04cf06df94a1cd3db8414890f1489829a279 [file] [log] [blame]
/*
* Copyright 2019-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;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import org.slf4j.Logger;
import com.google.common.annotations.Beta;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Abstract netconf session implements methods common
* for different implementations of netconf session.
*/
public abstract class AbstractNetconfSession implements NetconfSession {
private static final Logger log = getLogger(AbstractNetconfSession.class);
private static final String ENDPATTERN = "]]>]]>";
private static final String MESSAGE_ID_STRING = "message-id";
private static final String NEW_LINE = "\n";
private static final String EQUAL = "=";
private static final String RPC_OPEN = "<rpc ";
private static final String RPC_CLOSE = "</rpc>";
private static final String GET_OPEN = "<get>";
private static final String GET_CLOSE = "</get>";
private static final String WITH_DEFAULT_OPEN = "<with-defaults ";
private static final String WITH_DEFAULT_CLOSE = "</with-defaults>";
private static final String DEFAULT_OPERATION_OPEN = "<default-operation>";
private static final String DEFAULT_OPERATION_CLOSE = "</default-operation>";
private static final String SUBTREE_FILTER_OPEN = "<filter type=\"subtree\">";
private static final String SUBTREE_FILTER_CLOSE = "</filter>";
private static final String EDIT_CONFIG_OPEN = "<edit-config>";
private static final String EDIT_CONFIG_CLOSE = "</edit-config>";
private static final String GET_CONFIG_OPEN = "<get-config>";
private static final String GET_CONFIG_CLOSE = "</get-config>";
private static final String COPY_CONFIG_OPEN = "<copy-config>";
private static final String COPY_CONFIG_CLOSE = "</copy-config>";
private static final String DELETE_CONFIG_OPEN = "<delete-config>";
private static final String DELETE_CONFIG_CLOSE = "</delete-config>";
private static final String TARGET_OPEN = "<target>";
private static final String TARGET_CLOSE = "</target>";
private static final String SOURCE_OPEN = "<source>";
private static final String SOURCE_CLOSE = "</source>";
private static final String LOCK_OPEN = "<lock>";
private static final String LOCK_CLOSE = "</lock>";
private static final String UNLOCK_OPEN = "<lock>";
private static final String UNLOCK_CLOSE = "</lock>";
// FIXME hard coded namespace nc
private static final String CONFIG_OPEN = "<config xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\">";
private static final String CONFIG_CLOSE = "</config>";
private static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
private static final String NETCONF_BASE_NAMESPACE =
"xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"";
private static final String NETCONF_WITH_DEFAULTS_NAMESPACE =
"xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults\"";
@Override
public abstract CompletableFuture<String> request(String request) throws NetconfException;
@Override
public abstract CompletableFuture<String> rpc(String request) throws NetconfException;
protected CompletableFuture<CharSequence> executeRpc(String rpcString) throws NetconfException {
return rpc(rpcString)
.thenApply(msg -> {
// crude way of removing rpc-reply envelope
int begin = msg.indexOf("<data>");
int end = msg.lastIndexOf("</data>");
if (begin != -1 && end != -1) {
return msg.subSequence(begin, end + "</data>".length());
} else {
// FIXME probably should exceptionally fail here.
return msg;
}
});
}
@Override
public CompletableFuture<CharSequence> asyncGetConfig(DatastoreId datastore) throws NetconfException {
StringBuilder rpc = new StringBuilder();
rpc.append(RPC_OPEN);
rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
rpc.append(GET_CONFIG_OPEN).append(NEW_LINE);
rpc.append(SOURCE_OPEN).append(NEW_LINE);
rpc.append('<').append(checkNotNull(datastore)).append("/>");
rpc.append(SOURCE_CLOSE).append(NEW_LINE);
// filter here
rpc.append(GET_CONFIG_CLOSE).append(NEW_LINE);
rpc.append(RPC_CLOSE);
return executeRpc(rpc.toString());
}
@Override
public CompletableFuture<CharSequence> asyncGet() throws NetconfException {
StringBuilder rpc = new StringBuilder();
rpc.append(RPC_OPEN);
rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
rpc.append(GET_OPEN).append(NEW_LINE);
//filter here
rpc.append(GET_CLOSE).append(NEW_LINE);
rpc.append(RPC_CLOSE).append(NEW_LINE);
return executeRpc(rpc.toString());
}
@Override
public String get(String request) throws NetconfException {
return requestSync(request);
}
@Override
public String get(String filterSchema, String withDefaultsMode) throws NetconfException {
StringBuilder rpc = new StringBuilder(XML_HEADER);
rpc.append(RPC_OPEN);
rpc.append(MESSAGE_ID_STRING);
rpc.append(EQUAL);
rpc.append("\"");
//Assign a random integer here, it will be replaced in formatting
rpc.append(1);
rpc.append("\" ");
rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
rpc.append(GET_OPEN).append(NEW_LINE);
if (filterSchema != null) {
rpc.append(SUBTREE_FILTER_OPEN).append(NEW_LINE);
rpc.append(filterSchema).append(NEW_LINE);
rpc.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
}
if (withDefaultsMode != null) {
rpc.append(WITH_DEFAULT_OPEN).append(NETCONF_WITH_DEFAULTS_NAMESPACE).append(">");
rpc.append(withDefaultsMode).append(WITH_DEFAULT_CLOSE).append(NEW_LINE);
}
rpc.append(GET_CLOSE).append(NEW_LINE);
rpc.append(RPC_CLOSE).append(NEW_LINE);
rpc.append(ENDPATTERN);
return requestSync(rpc.toString());
}
@Override
public String doWrappedRpc(String request) throws NetconfException {
StringBuilder rpc = new StringBuilder(XML_HEADER);
rpc.append(RPC_OPEN);
rpc.append(MESSAGE_ID_STRING);
rpc.append(EQUAL);
rpc.append("\"");
//Assign a random integer here, it will be replaced in formatting
rpc.append(1);
rpc.append("\" ");
rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
rpc.append(request);
rpc.append(RPC_CLOSE).append(NEW_LINE);
rpc.append(ENDPATTERN);
return requestSync(rpc.toString());
}
@Override
public abstract String requestSync(String request) throws NetconfException;
@Override
public String getConfig(DatastoreId netconfTargetConfig) throws NetconfException {
return getConfig(netconfTargetConfig, null);
}
@Override
public String getConfig(DatastoreId netconfTargetConfig, String configurationFilterSchema) throws NetconfException {
StringBuilder rpc = new StringBuilder(XML_HEADER);
rpc.append(RPC_OPEN);
rpc.append(MESSAGE_ID_STRING);
rpc.append(EQUAL);
rpc.append("\"");
//Assign a random integer here, it will be replaced in formatting
rpc.append(1);
rpc.append("\" ");
rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
rpc.append(GET_CONFIG_OPEN).append(NEW_LINE);
rpc.append(SOURCE_OPEN).append(NEW_LINE);
rpc.append("<").append(netconfTargetConfig).append("/>");
rpc.append(SOURCE_CLOSE);
if (configurationFilterSchema != null) {
rpc.append(SUBTREE_FILTER_OPEN).append(NEW_LINE);
rpc.append(configurationFilterSchema).append(NEW_LINE);
rpc.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
}
rpc.append(GET_CONFIG_CLOSE).append(NEW_LINE);
rpc.append(RPC_CLOSE).append(NEW_LINE);
rpc.append(ENDPATTERN);
String reply = requestSync(rpc.toString());
return checkReply(reply) ? reply : "ERROR " + reply;
}
@Override
public boolean editConfig(String newConfiguration) throws NetconfException {
if (!newConfiguration.endsWith(ENDPATTERN)) {
newConfiguration = newConfiguration + ENDPATTERN;
}
return checkReply(requestSync(newConfiguration));
}
@Override
public boolean editConfig(DatastoreId netconfTargetConfig,
String mode,
String newConfiguration) throws NetconfException {
newConfiguration = newConfiguration.trim();
StringBuilder rpc = new StringBuilder(XML_HEADER);
rpc.append(RPC_OPEN);
rpc.append(MESSAGE_ID_STRING);
rpc.append(EQUAL);
rpc.append("\"");
//Assign a random integer here, it will be replaced in formatting
rpc.append(1);
rpc.append("\" ");
rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
rpc.append(EDIT_CONFIG_OPEN).append(NEW_LINE);
rpc.append(TARGET_OPEN);
rpc.append("<").append(netconfTargetConfig).append("/>");
rpc.append(TARGET_CLOSE).append(NEW_LINE);
if (mode != null) {
rpc.append(DEFAULT_OPERATION_OPEN);
rpc.append(mode);
rpc.append(DEFAULT_OPERATION_CLOSE).append(NEW_LINE);
}
rpc.append(CONFIG_OPEN).append(NEW_LINE);
rpc.append(newConfiguration);
rpc.append(CONFIG_CLOSE).append(NEW_LINE);
rpc.append(EDIT_CONFIG_CLOSE).append(NEW_LINE);
rpc.append(RPC_CLOSE);
rpc.append(ENDPATTERN);
String reply = requestSync(rpc.toString());
return checkReply(reply);
}
@Override
public boolean copyConfig(DatastoreId destination, DatastoreId source) throws NetconfException {
return checkReply(requestSync(bareCopyConfig(destination.asXml(), source.asXml())));
}
@Override
public boolean copyConfig(DatastoreId netconfTargetConfig, String newConfiguration) throws NetconfException {
return checkReply(requestSync(bareCopyConfig(netconfTargetConfig.asXml(),
normalizeCopyConfigParam(newConfiguration))));
}
@Override
public boolean copyConfig(String netconfTargetConfig, String newConfiguration) throws NetconfException {
return checkReply(requestSync(bareCopyConfig(normalizeCopyConfigParam(netconfTargetConfig),
normalizeCopyConfigParam(newConfiguration))));
}
@Override
public boolean deleteConfig(DatastoreId netconfTargetConfig) throws NetconfException {
if (netconfTargetConfig.equals(DatastoreId.RUNNING)) {
return false;
}
StringBuilder rpc = new StringBuilder(XML_HEADER);
rpc.append(RPC_OPEN).append(">");
rpc.append(DELETE_CONFIG_OPEN);
rpc.append(TARGET_OPEN);
rpc.append("<").append(netconfTargetConfig).append("/>");
rpc.append(TARGET_CLOSE);
rpc.append(DELETE_CONFIG_CLOSE);
rpc.append(RPC_CLOSE);
rpc.append(ENDPATTERN);
return checkReply(requestSync(rpc.toString()));
}
@Override
public void startSubscription() throws NetconfException {
startSubscription(null);
}
@Override
public abstract void startSubscription(String filterSchema) throws NetconfException;
@Override
public abstract void endSubscription() throws NetconfException;
@Override
public boolean lock(DatastoreId datastore) throws NetconfException {
StringBuilder rpc = new StringBuilder(XML_HEADER);
rpc.append(RPC_OPEN);
rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
rpc.append(LOCK_OPEN);
rpc.append(TARGET_OPEN);
rpc.append("<").append(datastore.id()).append("/>");
rpc.append(TARGET_CLOSE);
rpc.append(LOCK_CLOSE);
rpc.append(RPC_CLOSE);
rpc.append(ENDPATTERN);
String lockReply = requestSync(rpc.toString());
return checkReply(lockReply);
}
@Override
public boolean unlock(DatastoreId datastore) throws NetconfException {
StringBuilder rpc = new StringBuilder(XML_HEADER);
rpc.append(RPC_OPEN);
rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
rpc.append(UNLOCK_OPEN);
rpc.append(TARGET_OPEN);
rpc.append("<").append(datastore.id()).append("/>");
rpc.append(TARGET_CLOSE);
rpc.append(UNLOCK_CLOSE);
rpc.append(RPC_CLOSE);
rpc.append(ENDPATTERN);
String unlockReply = requestSync(rpc.toString());
return checkReply(unlockReply);
}
@Override
public boolean commit() throws NetconfException {
String rpc = "<rpc message-id=\"101\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"> <commit/></rpc>";
String reply = requestSync(rpc);
if (!checkReply(reply)) {
throw new NetconfException("Request not successful, with reply " + reply);
}
return true;
}
@Override
public boolean close() throws NetconfException {
StringBuilder rpc = new StringBuilder();
rpc.append(RPC_OPEN);
rpc.append(NETCONF_BASE_NAMESPACE).append(">");
rpc.append("<close-session/>");
rpc.append(RPC_CLOSE);
rpc.append(ENDPATTERN);
boolean closed = checkReply(requestSync(rpc.toString()));
if (closed) {
return closed;
} else {
//forcefully kill session if not closed
rpc = new StringBuilder();
rpc.append(RPC_OPEN);
rpc.append(NETCONF_BASE_NAMESPACE).append(">");
rpc.append("<kill-session/>");
rpc.append(RPC_CLOSE);
rpc.append(ENDPATTERN);
return checkReply(requestSync(rpc.toString()));
}
}
@Override
public abstract String getSessionId();
@Override
public abstract Set<String> getDeviceCapabilitiesSet();
@Override
public void addDeviceOutputListener(NetconfDeviceOutputEventListener listener) throws NetconfException {
throw new NetconfException("Only master session can call addDeviceOutputListener");
}
@Override
public void removeDeviceOutputListener(NetconfDeviceOutputEventListener listener) throws NetconfException {
throw new NetconfException("Only master session can call removeDeviceOutputListener");
}
/**
* Checks errors in reply from the session.
* @param reply reply string
* @return true if no error, else false
*/
@Beta
protected boolean checkReply(String reply) {
if (reply != null) {
if (!reply.contains("<rpc-error>")) {
return true;
} else if (reply.contains("<ok/>")
|| (reply.contains("<rpc-error>")
&& reply.contains("warning"))) {
// FIXME rpc-error with a warning is considered same as Ok??
return true;
}
}
return false;
}
private String bareCopyConfig(CharSequence target,
CharSequence source)
throws NetconfException {
StringBuilder rpc = new StringBuilder(XML_HEADER);
rpc.append(RPC_OPEN);
rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
rpc.append(COPY_CONFIG_OPEN);
rpc.append(TARGET_OPEN);
rpc.append(target);
rpc.append(TARGET_CLOSE);
rpc.append(SOURCE_OPEN);
rpc.append(source);
rpc.append(SOURCE_CLOSE);
rpc.append(COPY_CONFIG_CLOSE);
rpc.append(RPC_CLOSE);
rpc.append(ENDPATTERN);
return rpc.toString();
}
/**
* Normalize String parameter passed to copy-config API.
* <p>
* Provided for backward compatibility purpose
*
* @param input passed to copyConfig API
* @return XML likely to be suitable for copy-config source or target
*/
private CharSequence normalizeCopyConfigParam(String input) {
input = input.trim();
if (input.startsWith("<url")) {
return input;
} else if (!input.startsWith("<")) {
// assume it is a datastore name
return DatastoreId.datastore(input).asXml();
} else if (!input.startsWith("<config>")) {
return "<config>" + input + "</config>";
}
return input;
}
}