Andrea Campanella
Committed by Gerrit Code Review

[Goldeneye] ONOS-3939 Implementing Yang Sb XML utils

Change-Id: Ibf435f4c8e967ab793ec4a6d882df82b790f554f
/*
* Copyright 2016 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.drivers.utilities;
import com.google.common.base.MoreObjects;
import java.util.Map;
import java.util.Objects;
/**
* Class that contains the element base key and a map with all the values to
* set or retrieved with their relative key.
*/
public class YangElement {
private final String baseKey;
private final Map<String, String> keysAndValues;
public YangElement(String baseKey, Map<String, String> keysAndValues) {
this.baseKey = baseKey;
this.keysAndValues = keysAndValues;
}
public Map<String, String> getKeysAndValues() {
return keysAndValues;
}
public String getBaseKey() {
return baseKey;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("baseKey", baseKey)
.add("keysAndValues", keysAndValues)
.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
YangElement element = (YangElement) o;
if (baseKey != null ? !baseKey.equals(element.baseKey) : element.baseKey != null) {
return false;
}
return (keysAndValues == null ? element.keysAndValues == null :
keysAndValues.equals(element.keysAndValues));
}
@Override
public int hashCode() {
return Objects.hash(baseKey, keysAndValues);
}
}
/*
* Copyright 2016 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.drivers.utilities;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.HierarchicalConfiguration;
import org.apache.commons.configuration.XMLConfiguration;
import org.apache.commons.configuration.tree.ConfigurationNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import static org.onlab.util.Tools.nullIsNotFound;
/**
* Util CLass for Yang models.
* Clean abstraction to read, obtain and populate
* XML from Yang models translated into XML skeletons.
*/
public class YangXmlUtils {
public final Logger log = LoggerFactory
.getLogger(getClass());
private static YangXmlUtils instance = null;
//no instantiation, single instance.
protected YangXmlUtils() {
}
/**
* Retrieves a valid XML configuration for a specific XML path for a single
* instance of the Map specified key-value pairs.
*
* @param file path of the file to be used.
* @param values map of key and values to set under the generic path.
* @return Hierarchical configuration containing XML with values.
*/
public XMLConfiguration getXmlConfiguration(String file, Map<String, String> values) {
InputStream stream = getCfgInputStream(file);
XMLConfiguration cfg = loadXml(stream);
XMLConfiguration complete = new XMLConfiguration();
List<String> paths = new ArrayList<>();
Map<String, String> valuesWithKey = new HashMap<>();
values.keySet().stream().forEach(path -> {
List<String> allPaths = findPaths(cfg, path);
String key = nullIsNotFound(allPaths.isEmpty() ? null : allPaths.get(0),
"Yang model does not contain desired path");
paths.add(key);
valuesWithKey.put(key, values.get(path));
});
Collections.sort(paths, new StringLengthComparator());
paths.forEach(key -> complete.setProperty(key, valuesWithKey.get(key)));
addProperties(cfg, complete);
return complete;
}
/**
* Retrieves a valid XML configuration for a specific XML path for multiple
* instance of YangElements objects.
*
* @param file path of the file to be used.
* @param elements List of YangElements that are to be set.
* @return Hierachical configuration containing XML with values.
*/
public XMLConfiguration getXmlConfiguration(String file, List<YangElement> elements) {
InputStream stream = getCfgInputStream(file);
HierarchicalConfiguration cfg = loadXml(stream);
XMLConfiguration complete = new XMLConfiguration();
Multimap<String, YangElement> commonElements = ArrayListMultimap.create();
//saves the elements in a Multimap based on the computed key.
elements.forEach(element -> {
String completeKey = nullIsNotFound(findPath(cfg, element.getBaseKey()),
"Yang model does not contain desired path");
commonElements.put(completeKey, element);
});
//iterates over the elements and constructs the configuration
commonElements.keySet().forEach(key -> {
// if there is more than one element for a given path
if (commonElements.get(key).size() > 1) {
//creates a list of nodes that have to be added for that specific path
ArrayList<ConfigurationNode> nodes = new ArrayList<>();
//creates the nodes
commonElements.get(key).forEach(element -> nodes.add(getInnerNode(element).getRootNode()));
//computes the parent path
String parentPath = key.substring(0, key.lastIndexOf("."));
//adds the nodes to the complete configuration
complete.addNodes(parentPath, nodes);
} else {
//since there is only a single element we can assume it's the first one.
Map<String, String> keysAndValues = commonElements.get(key).stream().
findFirst().get().getKeysAndValues();
keysAndValues.forEach((k, v) -> complete.setProperty(key + "." + k, v));
}
});
addProperties(cfg, complete);
return complete;
}
//Adds all the properties of the original configuration to the new one.
private void addProperties(HierarchicalConfiguration cfg, HierarchicalConfiguration complete) {
cfg.getKeys().forEachRemaining(key -> {
String property = (String) cfg.getProperty(key);
if (!property.equals("")) {
complete.setProperty(key, property);
}
});
}
protected InputStream getCfgInputStream(String file) {
return getClass().getResourceAsStream(file);
}
/**
* Reads a valid XML configuration and returns a Map containing XML field name.
* and value contained for every subpath.
*
* @param cfg the Configuration to read.
* @param path path of the information to be read.
* @return list of elements containing baskey and map of key value pairs.
*/
public List<YangElement> readXmlConfiguration(HierarchicalConfiguration cfg, String path) {
List<YangElement> elements = new ArrayList<>();
String key = nullIsNotFound(findPath(cfg, path), "Configuration does not contain desired path");
getElements(cfg.configurationsAt(key), elements, key, cfg, path, key);
return ImmutableList.copyOf(elements);
}
private void getElements(List<HierarchicalConfiguration> configurations,
List<YangElement> elements, String basekey,
HierarchicalConfiguration originalCfg, String path,
String originalKey) {
//consider each sub configuration
configurations.stream().forEach(config -> {
YangElement element = new YangElement(path, new HashMap<>());
//for each of the keys of the sub configuration
config.getKeys().forEachRemaining(key -> {
//considers only one step ahead
//if one step ahead has other steps calls self to analize them
//else adds to yang element.
if (key.split("\\.").length > 1) {
getElements(originalCfg.configurationsAt(basekey + "." + key.split("\\.")[0]),
elements, basekey + "." + key.split("\\.")[0], originalCfg, path,
originalKey);
} else {
String replaced = basekey.replace(originalKey, "");
String partialKey = replaced.isEmpty() ? key : replaced.substring(1) + "." + key;
partialKey = partialKey.isEmpty() ? originalKey : partialKey;
//Adds values to the element with a subkey starting from the requeste path onwards
element.getKeysAndValues().put(partialKey, config.getProperty(key).toString());
}
});
//if the element doesnt already exist
if (!elements.contains(element) && !element.getKeysAndValues().isEmpty()) {
elements.add(element);
}
});
}
/**
* Single Instance of Yang utilities retriever.
*
* @return instance of YangXmlUtils
*/
public static YangXmlUtils getInstance() {
if (instance == null) {
instance = new YangXmlUtils();
}
return instance;
}
/**
* Return the string representation of the XMLConfig without header
* and configuration element.
*
* @param cfg the XML to convert
* @return the cfg string.
*/
public String getString(XMLConfiguration cfg) {
StringWriter stringWriter = new StringWriter();
try {
cfg.save(stringWriter);
} catch (ConfigurationException e) {
log.error("Cannot convert configuration", e.getMessage());
}
String xml = stringWriter.toString();
xml = xml.substring(xml.indexOf("\n"));
xml = xml.substring(xml.indexOf(">") + 1);
return xml;
}
/**
* Method to read an input stream into a XMLConfiguration.
* @param xmlStream inputstream containing XML description
* @return the XMLConfiguration object
*/
public XMLConfiguration loadXml(InputStream xmlStream) {
XMLConfiguration cfg = new XMLConfiguration();
try {
cfg.load(xmlStream);
return cfg;
} catch (ConfigurationException e) {
throw new IllegalArgumentException("Cannot load xml from Stream", e);
}
}
//Finds all paths for a corresponding element
private List<String> findPaths(HierarchicalConfiguration cfg, String path) {
List<String> paths = new ArrayList<>();
cfg.getKeys().forEachRemaining(key -> {
if (key.equals(path)) {
paths.add(key);
}
if (key.contains("." + path)) {
paths.add(key);
}
});
return paths;
}
//Finds the first parent path corresponding to an element.
private String findPath(HierarchicalConfiguration cfg, String element) {
Iterator<String> it = cfg.getKeys();
while (it.hasNext()) {
String key = it.next();
String[] arr = key.split("\\.");
for (int i = 0; i < arr.length; i++) {
if (element.equals(arr[i])) {
String completeKey = "";
for (int j = 0; j <= i; j++) {
completeKey = completeKey + "." + arr[j];
}
return completeKey.substring(1);
}
}
}
return null;
}
//creates a node based on a single Yang element.
private HierarchicalConfiguration getInnerNode(YangElement element) {
HierarchicalConfiguration node = new HierarchicalConfiguration();
node.setRoot(new HierarchicalConfiguration.Node(element.getBaseKey()));
element.getKeysAndValues().forEach(node::setProperty);
return node;
}
//String lenght comparator
private class StringLengthComparator implements Comparator<String> {
public int compare(String o1, String o2) {
if (o2 == null && o1 == null) {
return 0;
}
if (o1 == null) {
return o2.length();
}
if (o2 == null) {
return o1.length();
}
if (o1.length() != o2.length()) {
return o1.length() - o2.length(); //overflow impossible since lengths are non-negative
}
return o1.compareTo(o2);
}
}
}
/*
* Copyright 2016 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.drivers.utilities;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.collections.IteratorUtils;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.XMLConfiguration;
import org.junit.Before;
import org.junit.Test;
import org.onlab.packet.IpAddress;
import org.onosproject.net.behaviour.ControllerInfo;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.*;
/**
* Tests for the XMLYangUtils.
*/
public class YangXmlUtilsTest {
public static final String OF_CONFIG_XML_PATH = "/of-config/of-config.xml";
private YangXmlUtilsAdap utils;
private XMLConfiguration testCreateConfig;
@Before
public void setUp() throws Exception {
assertTrue("No resource for test", YangXmlUtilsTest.class.
getResourceAsStream("/of-config/of-config.xml") != null);
utils = new YangXmlUtilsAdap();
testCreateConfig = new XMLConfiguration();
}
/**
* Tests getting a single object configuration via passing the path and the map of the desired values.
*
* @throws ConfigurationException if the testing xml file is not there.
*/
@Test
public void testGetXmlUtilsInstance() throws ConfigurationException {
YangXmlUtils instance1 = YangXmlUtils.getInstance();
YangXmlUtils instance2 = YangXmlUtils.getInstance();
assertEquals("Duplicate instance", instance1, instance2);
}
/**
* Tests getting a single object configuration via passing the path and the map of the desired values.
*
* @throws ConfigurationException if the testing xml file is not there.
*/
@Test
public void testGetXmlConfigurationFromMap() throws ConfigurationException {
Map<String, String> pathAndValues = new HashMap<>();
pathAndValues.put("capable-switch.id", "openvswitch");
pathAndValues.put("switch.id", "ofc-bridge");
pathAndValues.put("controller.id", "tcp:1.1.1.1:1");
pathAndValues.put("controller.ip-address", "1.1.1.1");
XMLConfiguration cfg = utils.getXmlConfiguration(OF_CONFIG_XML_PATH, pathAndValues);
testCreateConfig.load(getClass().getResourceAsStream("/testCreateSingleYangConfig.xml"));
assertNotEquals("Null testConfiguration", new XMLConfiguration(), testCreateConfig);
assertEquals("Wrong configuaration", IteratorUtils.toList(testCreateConfig.getKeys()),
IteratorUtils.toList(cfg.getKeys()));
assertEquals("Wrong string configuaration", utils.getString(testCreateConfig),
utils.getString(cfg));
}
/**
* Tests getting a multiple object nested configuration via passing the path
* and a list of YangElements containing with the element and desired value.
*
* @throws ConfigurationException
*/
@Test
public void getXmlConfigurationFromYangElements() throws ConfigurationException {
assertNotEquals("Null testConfiguration", new XMLConfiguration(), testCreateConfig);
testCreateConfig.load(getClass().getResourceAsStream("/testYangConfig.xml"));
List<YangElement> elements = new ArrayList<>();
elements.add(new YangElement("capable-switch", ImmutableMap.of("id", "openvswitch")));
elements.add(new YangElement("switch", ImmutableMap.of("id", "ofc-bridge")));
List<ControllerInfo> controllers =
ImmutableList.of(new ControllerInfo(IpAddress.valueOf("1.1.1.1"), 1, "tcp"),
new ControllerInfo(IpAddress.valueOf("2.2.2.2"), 2, "tcp"));
controllers.stream().forEach(cInfo -> {
elements.add(new YangElement("controller", ImmutableMap.of("id", cInfo.target(),
"ip-address", cInfo.ip().toString())));
});
XMLConfiguration cfg =
new XMLConfiguration(YangXmlUtils.getInstance()
.getXmlConfiguration(OF_CONFIG_XML_PATH, elements));
assertEquals("Wrong configuaration", IteratorUtils.toList(testCreateConfig.getKeys()),
IteratorUtils.toList(cfg.getKeys()));
assertEquals("Wrong string configuaration", utils.getString(testCreateConfig),
utils.getString(cfg));
}
/**
* Test reading an XML configuration and retrieving the requested elements.
*
* @throws ConfigurationException
*/
@Test
public void testReadLastXmlConfiguration() throws ConfigurationException {
testCreateConfig.load(getClass().getResourceAsStream("/testYangConfig.xml"));
List<YangElement> elements = utils.readXmlConfiguration(testCreateConfig,
"controller");
List<YangElement> expected = ImmutableList.of(
new YangElement("controller", ImmutableMap.of("id", "tcp:1.1.1.1:1",
"ip-address", "1.1.1.1")),
new YangElement("controller", ImmutableMap.of("id", "tcp:2.2.2.2:2",
"ip-address", "2.2.2.2")));
assertEquals("Wrong elements collected", expected, elements);
}
/**
* Test reading an XML configuration and retrieving the requested elements.
*
* @throws ConfigurationException
*/
@Test
public void testReadNestedXmlConfiguration() throws ConfigurationException {
testCreateConfig.load(getClass().getResourceAsStream("/testYangConfig.xml"));
List<YangElement> elements = utils.readXmlConfiguration(testCreateConfig, "controllers");
List<YangElement> expected = ImmutableList.of(
new YangElement("controllers", ImmutableMap.of("controller.id", "tcp:1.1.1.1:1",
"controller.ip-address", "1.1.1.1")),
new YangElement("controllers", ImmutableMap.of("controller.id", "tcp:2.2.2.2:2",
"controller.ip-address", "2.2.2.2")));
assertEquals("Wrong elements collected", expected, elements);
}
//enables to change the path to the resources directory.
private class YangXmlUtilsAdap extends YangXmlUtils {
@Override
protected InputStream getCfgInputStream(String file) {
return YangXmlUtilsAdap.class.getResourceAsStream(file);
}
}
}
\ No newline at end of file
<?xml version='1.0' encoding='UTF-8'?>
<data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" />
\ No newline at end of file
<?xml version='1.0' encoding='UTF-8'?>
<!--
~ Copyright 2016 Open Networking Laboratory
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<capable-switch xmlns="urn:onf:config:yang">
<id/>
<config-version/>
<resources>
<port>
<name/>
<number/>
<requested-number/>
<current-rate/>
<max-rate/>
<configuration/>
<state>
<oper-state/>
<blocked/>
<live/>
</state>
<features>
<current>
<rate/>
<auto-negotiate/>
<medium/>
<pause/>
</current>
<advertised>
<rate/>
<medium/>
<pause/>
</advertised>
<supported>
<rate/>
<medium/>
<pause/>
</supported>
<advertised-peer>
<rate/>
<medium/>
<pause/>
</advertised-peer>
</features>
<tunnel>
<local-endpoint-ipv4-adress/>
<remote-endpoint-ipv4-adress/>
</tunnel>
<ipgre-tunnel>
<local-endpoint-ipv4-adress/>
<remote-endpoint-ipv4-adress/>
<key/>
</ipgre-tunnel>
<vxlan-tunnel>
<local-endpoint-ipv4-adress/>
<remote-endpoint-ipv4-adress/>
<vni/>
</vxlan-tunnel>
</port>
<queue>
<resource-id/>
<id/>
<port/>
<properties>
<min-rate/>
<max-rate/>
<experimenter-id/>
<experimenter-data/>
</properties>
</queue>
<owned-certificate>
<resource-id/>
<certificate/>
<private-key>
<key-type/>
<key-data/>
</private-key>
</owned-certificate>
<external-certificate>
<resource-id/>
<certificate/>
</external-certificate>
<flow-table>
<resource-id/>
<table-id/>
<name/>
<max-entries/>
</flow-table>
</resources>
<logical-switches>
<switch>
<id/>
<capabilities>
<max-buffered-packets/>
<max-tables/>
<max-ports/>
<reserved-port-types>
<type/>
</reserved-port-types>
<group-types>
<type/>
</group-types>
<group-capabilities>
<capability/>
</group-capabilities>
<action-types>
<type/>
</action-types>
<instruction-types>
<type/>
</instruction-types>
</capabilities>
<datapath-id/>
<controllers>
<controller>
<id/>
<ip-address/>
<local-ip-address/>
<state>
<connection-state/>
<local-ip-address-in-use/>
<local-port-in-use/>
</state>
</controller>
</controllers>
<resources>
<port/>
<queue/>
<certificate/>
<flow-table/>
</resources>
</switch>
</logical-switches>
</capable-switch>
</data>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<configuration xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<capable-switch xmlns="urn:onf:config:yang">
<id>openvswitch</id>
<logical-switches>
<switch>
<id>ofc-bridge</id>
<controllers>
<controller>
<id>tcp:1.1.1.1:1</id>
<ip-address>1.1.1.1</ip-address>
</controller>
</controllers>
</switch>
</logical-switches>
</capable-switch>
</configuration>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<configuration xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<capable-switch xmlns="urn:onf:config:yang">
<id>openvswitch</id>
<logical-switches>
<switch>
<id>ofc-bridge</id>
<controllers>
<controller>
<id>tcp:1.1.1.1:1</id>
<ip-address>1.1.1.1</ip-address>
</controller>
<controller>
<id>tcp:2.2.2.2:2</id>
<ip-address>2.2.2.2</ip-address>
</controller>
</controllers>
</switch>
</logical-switches>
</capable-switch>
</configuration>
\ No newline at end of file
#!/bin/bash
# -----------------------------------------------------------------------------
# ONOS YANG to XML skeleton convert via pyang
# -----------------------------------------------------------------------------
[ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
. $ONOS_ROOT/tools/build/envDefaults
aux=/tmp/pyangversion
pyang -v $cmd > $aux
errorstring="Pyang no installed, please download and intall from https://github.com/mbj4668/pyang"
cat $aux
grep -q "pyang: command not found" $aux && echo $errorString && exit 1
grep -q "pyang 1" $aux
if ! [ -e "$1" ]; then
echo "$1 input directory not found" >&2
exit 1
fi
if ! [ -d "$1" ]; then
echo "$1 not a directory for output" >&2
exit 1
fi
cd $1
find . -name '*.yang' | while read file; do f=$(basename $file ".yang"); \
directory=$ONOS_ROOT/drivers/utilities/src/main/resources/${PWD##*/}; \
if [ ! -d "$directory" ]; then
mkdir $directory; \
fi
echo $directory/$f.xml
pyang -f sample-xml-skeleton $f.yang > $directory/$f.xml; done
exit 0
\ No newline at end of file