propagate datastore exceptions all the way to northbound
[neutron.git] / neutron-hostconfig / vpp / src / main / java / org / opendaylight / neutron / hostconfig / vpp / NeutronHostconfigVppListener.java
1 /*
2  * Copyright (c) 2017 Intel Corporation. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.neutron.hostconfig.vpp;
10
11 import com.google.common.base.Preconditions;
12 import java.net.URI;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.List;
16 import java.util.Locale;
17 import java.util.Map;
18 import java.util.concurrent.ExecutorService;
19 import java.util.concurrent.Executors;
20 import java.util.stream.Collectors;
21 import javax.annotation.Nonnull;
22 import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener;
23 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
24 import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
25 import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener;
26 import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
27 import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
28 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
29 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
30 import org.opendaylight.neutron.hostconfig.utils.NeutronHostconfigUtils;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability;
34 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
35 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
36 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
37 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
38 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
39 import org.opendaylight.yangtools.concepts.ListenerRegistration;
40 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
41 import org.opendaylight.yangtools.yang.common.QName;
42 import org.opendaylight.yangtools.yang.common.Revision;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 public class NeutronHostconfigVppListener implements ClusteredDataTreeChangeListener<Node> {
47
48     private static final Logger LOG = LoggerFactory.getLogger(NeutronHostconfigVppListener.class);
49     private final DataBroker dataBroker;
50     private final NeutronHostconfigUtils neutronHostconfig;
51     private ListenerRegistration<DataTreeChangeListener<Node>> listenerRegistration;
52     private final ExecutorService executorService = Executors.newFixedThreadPool(1);
53
54     private static final TopologyId TOPOLOGY_NETCONF = new TopologyId("topology-netconf");
55     private static final QName V3PO_1704_CAPABILITY = QName.create(
56             URI.create("urn:opendaylight:params:xml:ns:yang:v3po"),
57             Revision.of("2017-03-15"), "v3po");
58     private static final QName V3PO_1701_CAPABILITY = QName.create(
59             URI.create("urn:opendaylight:params:xml:ns:yang:v3po"),
60             Revision.of("2016-12-14"), "v3po");
61     private static final QName INTERFACES_CAPABILITY =
62             QName.create(URI.create("urn:ietf:params:xml:ns:yang:ietf-interfaces"),
63                     Revision.of("2014-05-08"), "ietf-interfaces");
64     private static final List<QName> REQUIRED_CAPABILITIES = new ArrayList<>();
65     private final SocketInfo socketInfo;
66
67     public NeutronHostconfigVppListener(final DataBroker dataBroker, String spath, String sname, String vhostMode) {
68         LOG.info("Initializing Neutron-Hostconfig-Vpp-Listener");
69         this.dataBroker = Preconditions.checkNotNull(dataBroker);
70         final String vhostModeChecked = Preconditions.checkNotNull(vhostMode).toLowerCase(Locale.ROOT);
71         Preconditions.checkArgument(vhostModeChecked.equals("server") || vhostModeChecked.equals("client"),
72                 "Supported values for vhostuser-mode are client and server.");
73         this.socketInfo =
74                 new SocketInfo(Preconditions.checkNotNull(spath), Preconditions.checkNotNull(sname), vhostModeChecked);
75         this.neutronHostconfig = new NeutronHostconfigUtils(dataBroker);
76         REQUIRED_CAPABILITIES.add(V3PO_1704_CAPABILITY);
77         REQUIRED_CAPABILITIES.add(V3PO_1701_CAPABILITY);
78         REQUIRED_CAPABILITIES.add(INTERFACES_CAPABILITY);
79     }
80
81     @Override
82     public void onDataTreeChanged(@Nonnull Collection<DataTreeModification<Node>> changes) {
83         LOG.info("onDataTreeChanged: Received Data Tree Changed ...", changes);
84         executorService.execute(() -> {
85             try {
86                 for (DataTreeModification<Node> change : Preconditions.checkNotNull(changes,
87                         "Changes may not be null!")) {
88                     processDataTreeModification(change);
89                 }
90             } catch (TransactionCommitFailedException e) {
91                 LOG.error("Transaction commit failed; ignorining changes: ", changes, e);
92             }
93         });
94     }
95
96     private void processDataTreeModification(DataTreeModification<Node> change)
97             throws TransactionCommitFailedException {
98         final InstanceIdentifier<Node> key = change.getRootPath().getRootIdentifier();
99         final DataObjectModification<Node> mod = change.getRootNode();
100         LOG.info("onDataTreeChanged: Received Data Tree Changed Update of Type={} for Key={}",
101                 mod.getModificationType(), key);
102         switch (mod.getModificationType()) {
103             case SUBTREE_MODIFIED:
104                 if (validateVppNode(mod.getDataAfter())) {
105                     updateHostConfig(mod.getDataAfter(), NeutronHostconfigUtils.Action.UPDATE);
106                 } else {
107                     updateHostConfig(mod.getDataBefore(), NeutronHostconfigUtils.Action.DELETE);
108                 }
109                 break;
110             case DELETE:
111                 updateHostConfig(mod.getDataBefore(), NeutronHostconfigUtils.Action.DELETE);
112                 break;
113             case WRITE:
114                 if (validateVppNode(mod.getDataAfter())) {
115                     updateHostConfig(mod.getDataAfter(), NeutronHostconfigUtils.Action.ADD);
116                 }
117                 break;
118             default:
119         }
120     }
121
122     public void init() {
123         LOG.info("Initializing {}", getClass().getSimpleName());
124         DataTreeIdentifier<Node> dataTreeIdentifier = new DataTreeIdentifier<>(LogicalDatastoreType.OPERATIONAL,
125                 InstanceIdentifier.builder(NetworkTopology.class)
126                     .child(Topology.class, new TopologyKey(TOPOLOGY_NETCONF))
127                     .child(Node.class)
128                     .build());
129         listenerRegistration =
130                 dataBroker.registerDataTreeChangeListener(dataTreeIdentifier, NeutronHostconfigVppListener.this);
131         LOG.info("Registered listener to netconf nodes {}.", dataTreeIdentifier.getRootIdentifier());
132     }
133
134     private void updateHostConfig(Node node, NeutronHostconfigUtils.Action action)
135             throws TransactionCommitFailedException {
136         for (Map.Entry<String, String> entry : HostconfigUtil.createHostconfigsDataFor(node.getNodeId(), socketInfo)
137             .entrySet()) {
138             LOG.info("Updating hostconfig for node {}. Action: {}.", node.key(), action);
139             neutronHostconfig.updateMdsal(neutronHostconfig.buildHostConfigInfo(node.getNodeId().getValue(),
140                     entry.getKey(), entry.getValue()), action);
141         }
142     }
143
144     private boolean validateVppNode(Node node) {
145         LOG.info("Registering new node {}", node.getNodeId().getValue());
146         NetconfNode netconfNode = node.augmentation(NetconfNode.class);
147         if (netconfNode == null) {
148             LOG.warn("Node {} is not a netconf device", node.getNodeId().getValue());
149             return false;
150         }
151         NetconfNodeConnectionStatus.ConnectionStatus connectionStatus = netconfNode.getConnectionStatus();
152         switch (connectionStatus) {
153             case Connecting:
154                 LOG.info("Connecting device {} ...", node.getNodeId().getValue());
155                 break;
156             case Connected:
157                 if (isCapabilitiesPresent(netconfNode)) {
158                     LOG.warn("Node {} does not contain any capabilities", node.getNodeId().getValue());
159                     break;
160                 }
161                 if (!capabilityCheck(netconfNode.getAvailableCapabilities().getAvailableCapability())) {
162                     LOG.warn("Node {} does not contain all capabilities required by vpp-renderer",
163                             node.getNodeId().getValue());
164                     break;
165                 }
166                 LOG.info("VPP node connected {}", node.getNodeId().getValue());
167                 return true;
168             case UnableToConnect:
169                 LOG.warn("Unable to connect to node {}.", node.getNodeId().getValue());
170                 break;
171             default:
172         }
173         return false;
174     }
175
176     private boolean isCapabilitiesPresent(final NetconfNode netconfNode) {
177         return netconfNode.getAvailableCapabilities() == null
178                 || netconfNode.getAvailableCapabilities().getAvailableCapability() == null
179                 || netconfNode.getAvailableCapabilities().getAvailableCapability().isEmpty();
180     }
181
182     private boolean capabilityCheck(final List<AvailableCapability> capabilities) {
183         final List<String> availableCapabilities =
184                 capabilities.stream().map(AvailableCapability::getCapability).collect(Collectors.toList());
185         return REQUIRED_CAPABILITIES.stream().map(QName::toString).allMatch(availableCapabilities::contains);
186     }
187
188     public void close() throws Exception {
189         if (listenerRegistration != null) {
190             listenerRegistration.close();
191             LOG.info("HostConfig listener Closed");
192         }
193     }
194 }