import static java.util.Objects.requireNonNull;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Maps;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.net.URI;
+import java.util.Comparator;
import java.util.List;
-import java.util.Map;
+import java.util.Map.Entry;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy;
import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
+import org.opendaylight.restconf.server.api.OperationsContent;
import org.opendaylight.restconf.server.api.RestconfServer;
import org.opendaylight.restconf.server.spi.OperationInput;
import org.opendaylight.restconf.server.spi.OperationOutput;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.Revision;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
-import org.opendaylight.yangtools.yang.model.api.stmt.ActionEffectiveStatement;
import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
this.localRpcs = Maps.uniqueIndex(localRpcs, RpcImplementation::qname);
}
- public MdsalRestconfServer(final DatabindProvider databind, final DOMDataBroker dataBroker,
+ public MdsalRestconfServer(final DatabindProvider databindProvider, final DOMDataBroker dataBroker,
final DOMRpcService rpcService, final DOMMountPointService mountPointService,
final RpcImplementation... localRpcs) {
- this(databind, dataBroker, rpcService, mountPointService, List.of(localRpcs));
+ this(databindProvider, dataBroker, rpcService, mountPointService, List.of(localRpcs));
}
@NonNull InstanceIdentifierContext bindRequestPath(final String identifier) {
}
@Override
- public String operationsGET(final OperationsContent contentType) {
- return operationsGET(contentType, bindRequestRoot().inference());
+ public OperationsContent operationsGET() {
+ return operationsGET(databindProvider.currentContext().modelContext());
}
@Override
- public String operationsGET(final OperationsContent contentType, final String operation) {
- return operationsGET(contentType, bindRequestPath(operation).inference());
+ public OperationsContent operationsGET(final String operation) {
+ // get current module RPCs/actions by RPC/action name
+ final var inference = bindRequestPath(operation).inference();
+ if (inference.isEmpty()) {
+ return operationsGET(inference.getEffectiveModelContext());
+ }
+
+ final var stmt = inference.toSchemaInferenceStack().currentStatement();
+ if (stmt instanceof RpcEffectiveStatement rpc) {
+ final var qname = rpc.argument();
+ return new OperationsContent(inference.getEffectiveModelContext(),
+ ImmutableSetMultimap.of(qname.getModule(), qname));
+ }
+ LOG.debug("Operation '{}' resulted in non-RPC {}", operation, stmt);
+ return null;
}
- @VisibleForTesting
- static @NonNull String operationsGET(final OperationsContent contentType, final @NonNull Inference inference) {
- final var modelContext = inference.getEffectiveModelContext();
- if (modelContext.getModuleStatements().isEmpty()) {
+ private static @NonNull OperationsContent operationsGET(final EffectiveModelContext modelContext) {
+ final var modules = modelContext.getModuleStatements();
+ if (modules.isEmpty()) {
// No modules, or defensive return empty content
- return contentType.emptyBody;
+ return new OperationsContent(modelContext, ImmutableSetMultimap.of());
}
- if (inference.isEmpty()) {
- // empty stack == get all RPCs/actions
- return contentType.createBody(contentType.getModuleRpcs(modelContext, modelContext.getModuleStatements()));
- }
-
- // get current module RPCs/actions by RPC/action name
- final var stack = inference.toSchemaInferenceStack();
- final var currentModule = stack.currentModule();
- final var currentModuleKey = Map.of(currentModule.localQNameModule(), currentModule);
- final QName qname;
- final var stmt = stack.currentStatement();
- if (stmt instanceof RpcEffectiveStatement rpc) {
- qname = rpc.argument();
- } else if (stmt instanceof ActionEffectiveStatement action) {
- qname = action.argument();
- } else {
- throw new IllegalArgumentException("Unhandled statement " + stmt);
+ // RPCs by their XMLNamespace/Revision
+ final var table = HashBasedTable.<XMLNamespace, Revision, ImmutableSet<QName>>create();
+ for (var entry : modules.entrySet()) {
+ final var module = entry.getValue();
+ final var rpcNames = module.streamEffectiveSubstatements(RpcEffectiveStatement.class)
+ .map(RpcEffectiveStatement::argument)
+ .collect(ImmutableSet.toImmutableSet());
+ if (!rpcNames.isEmpty()) {
+ final var namespace = entry.getKey();
+ table.put(namespace.getNamespace(), namespace.getRevision().orElse(null), rpcNames);
+ }
}
- final var operName = qname.getLocalName();
- // FIXME: This is weird: it only handles rpc statements, not action statements. What is going on here?!
- // There is a reason this sort of method should handle both RPCs and actions, which is the invocation
- // remapping -- e.g. RFC8528 specifies how 'action' invocation is mappend to 'rpc' invocation.
- // There is something fishy going on here and we either have a bug, or the spec needs to be clarified.
- return contentType.getModuleRpcs(modelContext, currentModuleKey).stream()
- .findFirst()
- .map(e -> Map.entry(e.getKey(), e.getValue().stream().filter(operName::equals).toList()))
- .map(e -> contentType.createBody(List.of(e)))
- .orElse(contentType.emptyBody);
+ // Now pick the latest revision for each namespace
+ final var rpcs = ImmutableSetMultimap.<QNameModule, QName>builder();
+ for (var entry : table.rowMap().entrySet()) {
+ entry.getValue().entrySet().stream()
+ .sorted(Comparator.comparing(Entry::getKey, (first, second) -> Revision.compare(second, first)))
+ .findFirst()
+ .ifPresent(row -> rpcs.putAll(QNameModule.create(entry.getKey(), row.getKey()), row.getValue()));
+ }
+ return new OperationsContent(modelContext, rpcs.build());
}
@Override
+++ /dev/null
-/*
- * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others. All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.restconf.nb.rfc8040.rests.services.impl;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.yangtools.yang.common.QNameModule;
-import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
-import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
-import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
-
-/**
- * RESTCONF {@code /operations} content for a {@code GET} operation as per
- * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-3.3.2">RFC8040</a>.
- */
-public enum OperationsContent {
- JSON("{ \"ietf-restconf:operations\" : { } }") {
- @Override
- String createBody(final List<Entry<String, List<String>>> rpcsByPrefix) {
- final var sb = new StringBuilder("{\n"
- + " \"ietf-restconf:operations\" : {\n");
- var entryIt = rpcsByPrefix.iterator();
- var entry = entryIt.next();
- var nameIt = entry.getValue().iterator();
- while (true) {
- sb.append(" \"").append(entry.getKey()).append(':').append(nameIt.next()).append("\": [null]");
- if (nameIt.hasNext()) {
- sb.append(",\n");
- continue;
- }
-
- if (entryIt.hasNext()) {
- sb.append(",\n");
- entry = entryIt.next();
- nameIt = entry.getValue().iterator();
- continue;
- }
-
- break;
- }
-
- return sb.append("\n }\n}").toString();
- }
-
- @Override
- String prefix(final ModuleEffectiveStatement module) {
- return module.argument().getLocalName();
- }
- },
-
- XML("<operations xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"/>") {
- @Override
- String createBody(final List<Entry<String, List<String>>> rpcsByPrefix) {
- // Header with namespace declarations for each module
- final var sb = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
- + "<operations xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"");
- for (int i = 0; i < rpcsByPrefix.size(); ++i) {
- final var prefix = "ns" + i;
- sb.append("\n xmlns:").append(prefix).append("=\"").append(rpcsByPrefix.get(i).getKey())
- .append("\"");
- }
- sb.append(" >");
-
- // Second pass: emit all leaves
- for (int i = 0; i < rpcsByPrefix.size(); ++i) {
- final var prefix = "ns" + i;
- for (var localName : rpcsByPrefix.get(i).getValue()) {
- sb.append("\n <").append(prefix).append(':').append(localName).append("/>");
- }
- }
-
- return sb.append("\n</operations>").toString();
- }
-
- @Override
- String prefix(final ModuleEffectiveStatement module) {
- return module.localQNameModule().getNamespace().toString();
- }
- };
-
- public final @NonNull String emptyBody;
-
- OperationsContent(final String emptyBody) {
- this.emptyBody = requireNonNull(emptyBody);
- }
-
- /**
- * Returns a list of entries, where each entry contains a module prefix and a list of RPC names.
- *
- * @param context the effective model context
- * @param modules the map of QNameModule to ModuleEffectiveStatement
- * @return a list of entries, where each entry contains a module prefix and a list of RPC names
- */
- public List<Entry<@NonNull String, List<String>>> getModuleRpcs(final EffectiveModelContext context,
- final Map<QNameModule, ModuleEffectiveStatement> modules) {
- return modules.values().stream()
- // Extract XMLNamespaces
- .map(module -> module.localQNameModule().getNamespace())
- // Make sure each is XMLNamespace unique
- .distinct()
- // Find the most recent module with that namespace. This needed so we expose the right set of RPCs,
- // as we always pick the latest revision to resolve prefix (or module name).
- .map(namespace -> context.findModuleStatements(namespace).iterator().next())
- // Convert to module prefix + List<String> with RPC names
- .map(module -> Map.entry(prefix(module),
- module.streamEffectiveSubstatements(RpcEffectiveStatement.class)
- .map(rpc -> rpc.argument().getLocalName())
- .toList()))
- // Skip prefixes which do not have any RPCs
- .filter(entry -> !entry.getValue().isEmpty())
- // Ensure stability: sort by prefix
- .sorted(Entry.comparingByKey())
- .toList();
- }
-
- abstract @NonNull String createBody(List<Entry<String, List<String>>> rpcsByPrefix);
-
- abstract @NonNull String prefix(ModuleEffectiveStatement module);
-}
import javax.ws.rs.Consumes;
import javax.ws.rs.Encoded;
import javax.ws.rs.GET;
+import javax.ws.rs.NotFoundException;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
+import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.restconf.nb.rfc8040.MediaTypes;
import org.opendaylight.restconf.nb.rfc8040.databind.DatabindProvider;
import org.opendaylight.restconf.nb.rfc8040.databind.JsonOperationInputBody;
import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
import org.opendaylight.restconf.nb.rfc8040.databind.XmlOperationInputBody;
import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
+import org.opendaylight.restconf.server.api.OperationsContent;
import org.opendaylight.restconf.server.spi.OperationOutput;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
@Path("/operations")
@Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
public String getOperationsJSON() {
- return server.operationsGET(OperationsContent.JSON);
+ return server.operationsGET().toJSON();
}
/**
* Retrieve list of operations and actions supported by the server or device in JSON format.
*
- * @param identifier path parameter to identify device and/or operation
+ * @param operation path parameter to identify device and/or operation
* @return A string containing a JSON document conforming to both RFC8040 and RFC7951.
*/
@GET
- @Path("/operations/{identifier:.+}")
+ @Path("/operations/{operation:.+}")
@Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
- public String getOperationJSON(@PathParam("identifier") final String identifier) {
- return server.operationsGET(OperationsContent.JSON, identifier);
+ public String getOperationJSON(@PathParam("operation") final String operation) {
+ return operationsGET(operation).toJSON();
}
/**
@Path("/operations")
@Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
public String operationsGetXML() {
- return server.operationsGET(OperationsContent.XML);
+ return server.operationsGET().toXML();
}
/**
* Retrieve list of operations and actions supported by the server or device in XML format.
*
- * @param identifier path parameter to identify device and/or operation
+ * @param operation path parameter to identify device and/or operation
* @return A string containing an XML document conforming to both RFC8040 section 11.3.1 and page 84.
*/
@GET
- @Path("/operations/{identifier:.+}")
+ @Path("/operations/{operation:.+}")
@Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
- public String operationsGetXML(@PathParam("identifier") final String identifier) {
- return server.operationsGET(OperationsContent.XML, identifier);
+ public String operationsGetXML(@PathParam("operation") final String operation) {
+ return operationsGET(operation).toXML();
+ }
+
+ private @NonNull OperationsContent operationsGET(final String operation) {
+ final var content = server.operationsGET(operation);
+ if (content == null) {
+ throw new NotFoundException();
+ }
+ return content;
}
/**
--- /dev/null
+/*
+ * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.restconf.server.api;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ImmutableSetMultimap;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Map.Entry;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+
+/**
+ * RESTCONF {@code /operations} content for a {@code GET} operation as per
+ * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-3.3.2">RFC8040</a>.
+ */
+@NonNullByDefault
+public record OperationsContent(
+ EffectiveModelContext modelContext,
+ ImmutableSetMultimap<QNameModule, QName> operations) {
+ public OperationsContent {
+ requireNonNull(modelContext);
+ requireNonNull(operations);
+ }
+
+ public String toJSON() {
+ final var sb = new StringBuilder("""
+ {
+ "ietf-restconf:operations" : {\
+ """);
+
+ if (!operations.isEmpty()) {
+ final var entryIt = operations.asMap().entrySet().stream()
+ .map(entry -> Map.entry(
+ modelContext.findModuleStatement(entry.getKey()).orElseThrow().argument().getLocalName(),
+ entry.getValue()))
+ .sorted(Comparator.comparing(Entry::getKey))
+ .iterator();
+ var entry = entryIt.next();
+ var nameIt = entry.getValue().iterator();
+ while (true) {
+ final var rpcName = nameIt.next().getLocalName();
+ sb.append("\n \"").append(entry.getKey()).append(':').append(rpcName).append("\": [null]");
+ if (nameIt.hasNext()) {
+ sb.append(',');
+ continue;
+ }
+
+ if (entryIt.hasNext()) {
+ sb.append(',');
+ entry = entryIt.next();
+ nameIt = entry.getValue().iterator();
+ continue;
+ }
+
+ break;
+ }
+ }
+
+ return sb.append("\n }\n}").toString();
+ }
+
+ public String toXML() {
+ // Header with namespace declarations for each module
+ final var sb = new StringBuilder("""
+ <?xml version="1.0" encoding="UTF-8"?>
+ <operations xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"\
+ """);
+ if (operations.isEmpty()) {
+ return sb.append("/>").toString();
+ }
+
+ // We perform two passes:
+ // - first we emit namespace declarations
+ // - then we emit individual leaves
+ final var entries = operations.asMap().entrySet().stream()
+ .sorted(Comparator.comparing(Entry::getKey))
+ .toList();
+
+ for (int i = 0; i < entries.size(); ++i) {
+ sb.append("\n xmlns:ns").append(i).append("=\"").append(entries.get(i).getKey().getNamespace())
+ .append('"');
+ }
+ sb.append('>');
+
+ for (int i = 0; i < entries.size(); ++i) {
+ for (var rpc : entries.get(i).getValue()) {
+ sb.append("\n <ns").append(i).append(':').append(rpc.getLocalName()).append("/>");
+ }
+ }
+
+ return sb.append("\n</operations>").toString();
+ }
+}
import java.net.URI;
import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.restconf.api.ApiPath;
import org.opendaylight.restconf.common.errors.RestconfFuture;
import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
-import org.opendaylight.restconf.nb.rfc8040.rests.services.impl.OperationsContent;
import org.opendaylight.restconf.server.spi.OperationOutput;
/**
/**
* Return the set of supported RPCs supported by {@link #operationsPOST(URI, String, OperationInputBody)}.
*
- * @param contentType Formatting type
- * @return A formatted string
+ * @return An {@link OperationsContent}
*/
- String operationsGET(OperationsContent contentType);
+ OperationsContent operationsGET();
/*
* Return the details about a particular operation supported by
* {@link #operationsPOST(URI, String, OperationInputBody)}, as expressed in the
* <a href="https://www.rfc-editor.org/rfc/rfc8040#page-84>RFC8040<a> {@code container operations} statement.
*
- * @param contentType Formatting type
* @param operation An operation
- * @return A formatted string
+ * @return An {@link OperationsContent}, or {@code null} if {@code operation} does not point to an {@code rpc}
*/
// FIXME: 'operation' should really be an ApiIdentifier with non-null module, but we also support ang-ext:mount,
// and hence it is a path right now
// FIXME: use ApiPath instead of String
- String operationsGET(OperationsContent contentType, String operation);
+ @Nullable OperationsContent operationsGET(String operation);
/**
* Invoke an RPC operation, as defined in
*/
package org.opendaylight.restconf.nb.rfc8040.rests.services.impl;
-import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
-import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.eclipse.jdt.annotation.NonNull;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.opendaylight.mdsal.dom.api.DOMDataBroker;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
+import org.opendaylight.mdsal.dom.api.DOMRpcService;
+import org.opendaylight.restconf.nb.rfc8040.databind.DatabindContext;
import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
-public class Netconf822Test {
- private static final Absolute NEW1 = Absolute.of(QName.create("foo", "2021-09-30", "new1"));
+@ExtendWith(MockitoExtension.class)
+class Netconf822Test {
+ private static final @NonNull DatabindContext DATABIND =
+ DatabindContext.ofModel(YangParserTestUtils.parseYangResourceDirectory("/nc822"));
- private static EffectiveModelContext SCHEMA;
+ @Mock
+ private DOMDataBroker dataBroker;
+ @Mock
+ private DOMRpcService rpcService;
+ @Mock
+ private DOMMountPointService mountPointService;
- @BeforeClass
- public static void beforeClass() {
- SCHEMA = YangParserTestUtils.parseYangResourceDirectory("/nc822");
+ private MdsalRestconfServer server;
+
+ @BeforeEach
+ void beforeEach() {
+ server = new MdsalRestconfServer(() -> DATABIND, dataBroker, rpcService, mountPointService);
}
@Test
- public void testOperationsContentJSON() {
+ void testOperationsContent() {
+ final var content = server.operationsGET();
assertEquals("""
{
"ietf-restconf:operations" : {
"foo:new": [null],
"foo:new1": [null]
}
- }""",
- MdsalRestconfServer.operationsGET(OperationsContent.JSON, SchemaInferenceStack.of(SCHEMA).toInference()));
- }
-
- @Test
- public void testOperationsContentByIdentifierJSON() {
- assertEquals("""
- {
- "ietf-restconf:operations" : {
- "foo:new1": [null]
- }
- }""",
- MdsalRestconfServer.operationsGET(OperationsContent.JSON,
- SchemaInferenceStack.of(SCHEMA, NEW1).toInference()));
- }
-
- @Test
- public void testOperationsContentXML() {
+ }""", content.toJSON());
assertEquals("""
<?xml version="1.0" encoding="UTF-8"?>
<operations xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"
- xmlns:ns0="foo" >
+ xmlns:ns0="foo">
<ns0:new/>
<ns0:new1/>
- </operations>""",
- MdsalRestconfServer.operationsGET(OperationsContent.XML, SchemaInferenceStack.of(SCHEMA).toInference()));
+ </operations>""", content.toXML());
}
@Test
- public void testOperationsContentByIdentifierXML() {
+ void testOperationsContentByIdentifier() {
+ final var content = server.operationsGET("foo:new1");
+ assertNotNull(content);
+ assertEquals("""
+ {
+ "ietf-restconf:operations" : {
+ "foo:new1": [null]
+ }
+ }""", content.toJSON());
assertEquals("""
<?xml version="1.0" encoding="UTF-8"?>
<operations xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"
- xmlns:ns0="foo" >
+ xmlns:ns0="foo">
<ns0:new1/>
- </operations>""",
- MdsalRestconfServer.operationsGET(OperationsContent.XML,
- SchemaInferenceStack.of(SCHEMA, NEW1).toInference()));
+ </operations>""", content.toXML());
}
}
<?xml version="1.0" encoding="UTF-8"?>
<operations xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"
xmlns:ns0="module:1"
- xmlns:ns1="module:2" >
+ xmlns:ns1="module:2">
<ns0:dummy-rpc1-module1/>
<ns0:dummy-rpc2-module1/>
<ns1:dummy-rpc1-module2/>
assertEquals("""
<?xml version="1.0" encoding="UTF-8"?>
<operations xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf"
- xmlns:ns0="module:1" >
+ xmlns:ns0="module:1">
<ns0:dummy-rpc1-module1/>
</operations>""", operationXML);
}