package org.opendaylight.controller.sal.connect.netconf.listener; import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import java.util.Collection; import java.util.HashSet; import java.util.Set; import org.opendaylight.controller.netconf.client.NetconfClientSession; import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; import org.opendaylight.yangtools.yang.common.QName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public final class NetconfSessionCapabilities { private static final class ParameterMatcher { private final Predicate predicate; private final int skipLength; ParameterMatcher(final String name) { predicate = new Predicate() { @Override public boolean apply(final String input) { return input.startsWith(name); } }; this.skipLength = name.length(); } private String from(final Iterable params) { final Optional o = Iterables.tryFind(params, predicate); if (!o.isPresent()) { return null; } return o.get().substring(skipLength); } } private static final Logger LOG = LoggerFactory.getLogger(NetconfSessionCapabilities.class); private static final ParameterMatcher MODULE_PARAM = new ParameterMatcher("module="); private static final ParameterMatcher REVISION_PARAM = new ParameterMatcher("revision="); private static final ParameterMatcher BROKEN_REVISON_PARAM = new ParameterMatcher("amp;revision="); private static final Splitter AMP_SPLITTER = Splitter.on('&'); private static final Predicate CONTAINS_REVISION = new Predicate() { @Override public boolean apply(final String input) { return input.contains("revision="); } }; private final Set moduleBasedCaps; private final Set nonModuleCaps; private NetconfSessionCapabilities(final Set nonModuleCaps, final Set moduleBasedCaps) { this.nonModuleCaps = Preconditions.checkNotNull(nonModuleCaps); this.moduleBasedCaps = Preconditions.checkNotNull(moduleBasedCaps); } public Set getModuleBasedCaps() { return moduleBasedCaps; } public Set getNonModuleCaps() { return nonModuleCaps; } public boolean containsNonModuleCapability(final String capability) { return nonModuleCaps.contains(capability); } public boolean containsModuleCapability(final QName capability) { return moduleBasedCaps.contains(capability); } @Override public String toString() { return Objects.toStringHelper(this) .add("capabilities", nonModuleCaps) .add("moduleBasedCapabilities", moduleBasedCaps) .add("rollback", isRollbackSupported()) .add("monitoring", isMonitoringSupported()) .toString(); } public boolean isRollbackSupported() { return containsNonModuleCapability(NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString()); } public boolean isCandidateSupported() { return containsNonModuleCapability(NetconfMessageTransformUtil.NETCONF_CANDIDATE_URI.toString()); } public boolean isMonitoringSupported() { return containsModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING) || containsNonModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString()); } public NetconfSessionCapabilities replaceModuleCaps(final NetconfSessionCapabilities netconfSessionModuleCapabilities) { final Set moduleBasedCaps = Sets.newHashSet(netconfSessionModuleCapabilities.getModuleBasedCaps()); // Preserve monitoring module, since it indicates support for ietf-netconf-monitoring if(containsModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING)) { moduleBasedCaps.add(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING); } return new NetconfSessionCapabilities(getNonModuleCaps(), moduleBasedCaps); } public static NetconfSessionCapabilities fromNetconfSession(final NetconfClientSession session) { return fromStrings(session.getServerCapabilities()); } public static NetconfSessionCapabilities fromStrings(final Collection capabilities) { final Set moduleBasedCaps = new HashSet<>(); final Set nonModuleCaps = Sets.newHashSet(capabilities); for (final String capability : capabilities) { final int qmark = capability.indexOf('?'); if (qmark == -1) { continue; } final String namespace = capability.substring(0, qmark); final Iterable queryParams = AMP_SPLITTER.split(capability.substring(qmark + 1)); final String moduleName = MODULE_PARAM.from(queryParams); if (moduleName == null) { continue; } String revision = REVISION_PARAM.from(queryParams); if (revision != null) { moduleBasedCaps.add(QName.create(namespace, revision, moduleName)); nonModuleCaps.remove(capability); continue; } /* * We have seen devices which mis-escape revision, but the revision may not * even be there. First check if there is a substring that matches revision. */ if (!Iterables.any(queryParams, CONTAINS_REVISION)) { continue; } LOG.debug("Netconf device was not reporting revision correctly, trying to get amp;revision="); revision = BROKEN_REVISON_PARAM.from(queryParams); if (revision == null) { LOG.warn("Netconf device returned revision incorrectly escaped for {}, ignoring it", capability); } // FIXME: do we really want to continue here? moduleBasedCaps.add(QName.cachedReference(QName.create(namespace, revision, moduleName))); nonModuleCaps.remove(capability); } return new NetconfSessionCapabilities(ImmutableSet.copyOf(nonModuleCaps), ImmutableSet.copyOf(moduleBasedCaps)); } }