Merge "Refactor TAPI Connectivity Utils"
[transportpce.git] / tapi / src / main / java / org / opendaylight / transportpce / tapi / listeners / TapiPceNotificationHandler.java
1 /*
2  * Copyright © 2021 Nokia, Inc. and others.  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 package org.opendaylight.transportpce.tapi.listeners;
9
10 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
11 import java.nio.charset.Charset;
12 import java.util.HashMap;
13 import java.util.Map;
14 import java.util.Optional;
15 import java.util.Set;
16 import java.util.UUID;
17 import java.util.concurrent.ExecutionException;
18 import org.opendaylight.mdsal.binding.api.DataBroker;
19 import org.opendaylight.mdsal.binding.api.NotificationService.CompositeListener;
20 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
21 import org.opendaylight.transportpce.common.network.NetworkTransactionImpl;
22 import org.opendaylight.transportpce.common.network.NetworkTransactionService;
23 import org.opendaylight.transportpce.tapi.connectivity.ConnectivityUtils;
24 import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev240205.ServicePathRpcResult;
25 import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev240205.service.path.rpc.result.PathDescription;
26 import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev240205.service.path.rpc.result.PathDescriptionBuilder;
27 import org.opendaylight.yang.gen.v1.http.org.transportpce.b.c._interface.service.types.rev220118.RpcStatusEx;
28 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.common.rev221121.Context;
29 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.common.rev221121.Uuid;
30 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.Context1;
31 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.CreateConnectivityServiceInput;
32 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.OwnedNodeEdgePoint1;
33 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.OwnedNodeEdgePoint1Builder;
34 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.cep.list.ConnectionEndPoint;
35 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.connectivity.context.ConnectivityService;
36 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.connectivity.context.ConnectivityServiceBuilder;
37 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.connectivity.context.ConnectivityServiceKey;
38 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.connectivity.service.Connection;
39 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.connectivity.service.ConnectionKey;
40 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.context.ConnectivityContextBuilder;
41 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.context.topology.context.topology.node.owned.node.edge.point.CepList;
42 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.context.topology.context.topology.node.owned.node.edge.point.CepListBuilder;
43 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.topology.rev221121.node.OwnedNodeEdgePoint;
44 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.topology.rev221121.node.OwnedNodeEdgePointBuilder;
45 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.topology.rev221121.node.OwnedNodeEdgePointKey;
46 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.topology.rev221121.topology.NodeKey;
47 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.topology.rev221121.topology.context.Topology;
48 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.topology.rev221121.topology.context.TopologyKey;
49 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 public class TapiPceNotificationHandler {
54
55     private static final Logger LOG = LoggerFactory.getLogger(TapiPceNotificationHandler.class);
56
57     private ServicePathRpcResult servicePathRpcResult;
58     private CreateConnectivityServiceInput input;
59     private Uuid serviceUuid;
60     private final DataBroker dataBroker;
61     private final NetworkTransactionService networkTransactionService;
62     private final ConnectivityUtils connectivityUtils;
63     private final Map<org.opendaylight.yang.gen.v1.urn
64         .onf.otcc.yang.tapi.connectivity.rev221121.connectivity.context.ConnectionKey,
65         org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.connectivity.context.Connection>
66         connectionFullMap; // this variable is for complete connection objects
67
68     public TapiPceNotificationHandler(DataBroker dataBroker, ConnectivityUtils connecUtil) {
69         this.connectionFullMap = new HashMap<>();
70         this.dataBroker = dataBroker;
71         this.networkTransactionService = new NetworkTransactionImpl(this.dataBroker);
72         this.connectivityUtils = connecUtil;
73     }
74
75     public CompositeListener getCompositeListener() {
76         return new CompositeListener(Set.of(
77             new CompositeListener.Component<>(ServicePathRpcResult.class, this::onServicePathRpcResult)));
78     }
79
80     private void onServicePathRpcResult(ServicePathRpcResult notification) {
81         if (compareServicePathRpcResult(notification)) {
82             LOG.warn("ServicePathRpcResult already wired !");
83             return;
84         }
85         servicePathRpcResult = notification;
86         switch (servicePathRpcResult.getNotificationType().getIntValue()) {
87             /* path-computation-request. */
88             case 1:
89                 onPathComputationResult(notification);
90                 break;
91             /* cancel-resource-reserve. */
92             case 2:
93                 onCancelResourceResult(notification.getServiceName());
94                 break;
95             default:
96                 break;
97         }
98     }
99
100     /**
101      * Process path computation request result.
102      * @param notification the result notification.
103      */
104     private void onPathComputationResult(ServicePathRpcResult notification) {
105         this.connectionFullMap.clear();
106         LOG.info("PCE '{}' Notification received : {}",servicePathRpcResult.getNotificationType().getName(),
107             notification);
108         if (servicePathRpcResult.getStatus() == RpcStatusEx.Failed) {
109             LOG.error("PCE path computation failed !");
110             return;
111         } else if (servicePathRpcResult.getStatus() == RpcStatusEx.Pending) {
112             LOG.warn("PCE path computation returned a Penging RpcStatusEx code!");
113             return;
114         } else if (servicePathRpcResult.getStatus() != RpcStatusEx.Successful) {
115             LOG.error("PCE path computation returned an unknown RpcStatusEx code!");
116             return;
117         }
118
119         LOG.info("PCE calculation done OK !");
120         if (servicePathRpcResult.getPathDescription() == null) {
121             LOG.error("'PathDescription' parameter is null ");
122             return;
123         }
124         PathDescription pathDescription = new PathDescriptionBuilder()
125             .setAToZDirection(servicePathRpcResult.getPathDescription().getAToZDirection())
126             .setZToADirection(servicePathRpcResult.getPathDescription().getZToADirection())
127             .build();
128         LOG.info("PathDescription for TAPI gets : {}", pathDescription);
129         if (input == null) {
130             LOG.error("Input is null !");
131             return;
132         }
133         // TODO: check kind of service: based on the device Id of the input,
134         //  verify the type of XPDR and the capacity and determine if it is an OTN service or pure WDM service
135         // Create connections and ceps for the connectivity service.
136         //  Connections must be with a locked stated. As the renderer hasnt implemented yet the oc's
137         Map<ConnectionKey, Connection> connectionMap = connectivityUtils.createConnectionsFromService(
138                 pathDescription, input.getLayerProtocolName());
139         this.connectionFullMap.putAll(connectivityUtils.getConnectionFullMap());
140         LOG.debug("Connection Map from createConnectionsAndCepsForService is {}, LAYERPROTOCOL of service is {} ",
141             connectionMap.toString(), input.getLayerProtocolName());
142         // add connections to connection context and to connectivity context
143         updateConnectionContextWithConn(this.connectionFullMap, connectionMap, serviceUuid);
144     }
145
146     /**
147      * Process cancel resource result.
148      * @param serviceName Service name to build uuid.
149      */
150     private void onCancelResourceResult(String serviceName) {
151         if (servicePathRpcResult.getStatus() == RpcStatusEx.Failed) {
152             LOG.info("PCE cancel resource failed !");
153             return;
154         } else if (servicePathRpcResult.getStatus() == RpcStatusEx.Pending) {
155             LOG.warn("PCE cancel returned a Penging RpcStatusEx code!");
156             return;
157         } else if (servicePathRpcResult.getStatus() != RpcStatusEx.Successful) {
158             LOG.error("PCE cancel returned an unknown RpcStatusEx code!");
159             return;
160         }
161         LOG.info("PCE cancel resource done OK !");
162         Uuid suuid = new Uuid(UUID.nameUUIDFromBytes(serviceName.getBytes(Charset.forName("UTF-8")))
163             .toString());
164         // get connections of connectivity service and remove them from tapi context and then remove
165         //  service from context. The CEPs are maintained as they could be reused by another service
166         ConnectivityService connService = getConnectivityService(suuid);
167         if (connService == null) {
168             LOG.error("Service doesnt exist in tapi context");
169             return;
170         }
171         for (Connection connection:connService.getConnection().values()) {
172             deleteConnection(connection.getConnectionUuid());
173         }
174         deleteConnectivityService(suuid);
175     }
176
177     @SuppressFBWarnings(
178         value = "ES_COMPARING_STRINGS_WITH_EQ",
179         justification = "false positives, not strings but real object references comparisons")
180     private Boolean compareServicePathRpcResult(ServicePathRpcResult notification) {
181         if (servicePathRpcResult == null) {
182             return false;
183         }
184         if (servicePathRpcResult.getNotificationType() != notification.getNotificationType()) {
185             return false;
186         }
187         if (servicePathRpcResult.getServiceName() != notification.getServiceName()) {
188             return false;
189         }
190         if (servicePathRpcResult.getStatus() != notification.getStatus()) {
191             return false;
192         }
193         if (servicePathRpcResult.getStatusMessage() != notification.getStatusMessage()) {
194             return false;
195         }
196         return true;
197     }
198
199
200     public void updateTopologyWithCep(Uuid topoUuid, Uuid nodeUuid, Uuid nepUuid, ConnectionEndPoint cep) {
201         // TODO: verify this is correct. Should we identify the context IID with the context UUID??
202         InstanceIdentifier<OwnedNodeEdgePoint> onepIID = InstanceIdentifier.builder(Context.class)
203             .augmentation(org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.topology.rev221121.Context1.class)
204             .child(org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.topology.rev221121.context.TopologyContext.class)
205             .child(Topology.class, new TopologyKey(topoUuid))
206             .child(org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.topology.rev221121.topology.Node.class,
207                 new NodeKey(nodeUuid))
208             .child(OwnedNodeEdgePoint.class, new OwnedNodeEdgePointKey(nepUuid))
209             .build();
210         try {
211             Optional<OwnedNodeEdgePoint> optionalOnep = this.networkTransactionService.read(
212                 LogicalDatastoreType.OPERATIONAL, onepIID).get();
213             if (!optionalOnep.isPresent()) {
214                 LOG.error("ONEP is not present in datastore");
215                 return;
216             }
217             OwnedNodeEdgePoint onep = optionalOnep.orElseThrow();
218             LOG.info("ONEP found = {}", onep.toString());
219             // TODO -> If cep exists -> skip merging to datasore
220             OwnedNodeEdgePoint1 onep1 = onep.augmentation(OwnedNodeEdgePoint1.class);
221             if (onep1 != null && onep1.getCepList() != null && onep1.getCepList().getConnectionEndPoint() != null) {
222                 if (onep1.getCepList().getConnectionEndPoint().containsKey(
223                         new org.opendaylight.yang.gen.v1
224                             .urn.onf.otcc.yang.tapi.connectivity.rev221121.cep.list.ConnectionEndPointKey(cep.key()))) {
225                     LOG.info("CEP already in topology, skipping merge");
226                     return;
227                 }
228             }
229             // Updated ONEP
230             CepList cepList = new CepListBuilder().setConnectionEndPoint(Map.of(cep.key(), cep)).build();
231             OwnedNodeEdgePoint1 onep1Bldr = new OwnedNodeEdgePoint1Builder().setCepList(cepList).build();
232             OwnedNodeEdgePoint newOnep = new OwnedNodeEdgePointBuilder(onep)
233                 .addAugmentation(onep1Bldr)
234                 .build();
235             LOG.info("New ONEP is {}", newOnep.toString());
236             // merge in datastore
237             this.networkTransactionService.merge(LogicalDatastoreType.OPERATIONAL, onepIID,
238                 newOnep);
239             this.networkTransactionService.commit().get();
240             LOG.info("CEP added successfully.");
241         } catch (InterruptedException | ExecutionException e) {
242             LOG.error("Couldnt update cep in topology", e);
243         }
244     }
245
246     public void updateTopologyWithNep(Uuid topoUuid, Uuid nodeUuid, Uuid nepUuid, OwnedNodeEdgePoint onep) {
247         // TODO: verify this is correct. Should we identify the context IID with the context UUID??
248         InstanceIdentifier<OwnedNodeEdgePoint> onepIID = InstanceIdentifier.builder(Context.class)
249             .augmentation(org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.topology.rev221121.Context1.class)
250             .child(org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.topology.rev221121.context.TopologyContext.class)
251             .child(Topology.class, new TopologyKey(topoUuid))
252             .child(org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.topology.rev221121.topology.Node.class,
253                 new NodeKey(nodeUuid))
254             .child(OwnedNodeEdgePoint.class, new OwnedNodeEdgePointKey(nepUuid))
255             .build();
256         try {
257             Optional<OwnedNodeEdgePoint> optionalOnep = this.networkTransactionService.read(
258                 LogicalDatastoreType.OPERATIONAL, onepIID).get();
259             if (optionalOnep.isPresent()) {
260                 LOG.error("ONEP is already present in datastore");
261                 return;
262             }
263             // merge in datastore
264             this.networkTransactionService.merge(LogicalDatastoreType.OPERATIONAL, onepIID,
265                 onep);
266             this.networkTransactionService.commit().get();
267             LOG.info("NEP {} added successfully.", onep.getName().toString());
268         } catch (InterruptedException | ExecutionException e) {
269             LOG.error("Couldnt put NEP {} in topology, error = ", onep.getName().toString(), e);
270         }
271     }
272
273
274     private void updateConnectionContextWithConn(
275             Map<org.opendaylight.yang.gen.v1.urn
276                     .onf.otcc.yang.tapi.connectivity.rev221121.connectivity.context.ConnectionKey,
277                 org.opendaylight.yang.gen.v1.urn
278                     .onf.otcc.yang.tapi.connectivity.rev221121.connectivity.context.Connection> connFullMap,
279             Map<ConnectionKey, Connection> connMap, Uuid suuid) {
280         // TODO: verify this is correct. Should we identify the context IID with the context UUID??
281         try {
282             ConnectivityService connServ = getConnectivityService(suuid);
283             ConnectivityService updtConnServ = new ConnectivityServiceBuilder(connServ)
284                 .setConnection(connMap)
285                 .build();
286
287             // Perform the merge operation with the new conn service and the connection context updated
288             org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.context.ConnectivityContext
289                 connectivityContext = new ConnectivityContextBuilder()
290                     .setConnectivityService(Map.of(updtConnServ.key(), updtConnServ))
291                     .setConnection(connFullMap)
292                     .build();
293             InstanceIdentifier<org.opendaylight.yang.gen.v1.urn
294                     .onf.otcc.yang.tapi.connectivity.rev221121.context.ConnectivityContext> connectivitycontextIID =
295                 InstanceIdentifier.builder(Context.class).augmentation(Context1.class)
296                     .child(org.opendaylight.yang.gen.v1.urn
297                         .onf.otcc.yang.tapi.connectivity.rev221121.context.ConnectivityContext.class)
298                     .build();
299             // merge in datastore
300             this.networkTransactionService.merge(LogicalDatastoreType.OPERATIONAL, connectivitycontextIID,
301                 connectivityContext);
302             this.networkTransactionService.commit().get();
303             LOG.info("TAPI connectivity merged successfully.");
304         } catch (InterruptedException | ExecutionException e) {
305             LOG.error("Failed to merge TAPI connectivity", e);
306         }
307     }
308
309     private ConnectivityService getConnectivityService(Uuid suuid) {
310         try {
311             // First read connectivity service with service uuid and update info
312             InstanceIdentifier<ConnectivityService> connectivityServIID =
313                 InstanceIdentifier.builder(Context.class).augmentation(Context1.class)
314                     .child(org.opendaylight.yang.gen.v1.urn
315                         .onf.otcc.yang.tapi.connectivity.rev221121.context.ConnectivityContext.class)
316                     .child(ConnectivityService.class, new ConnectivityServiceKey(suuid))
317                     .build();
318
319             Optional<ConnectivityService> optConnServ =
320                 this.networkTransactionService.read(LogicalDatastoreType.OPERATIONAL, connectivityServIID).get();
321             if (optConnServ.isEmpty()) {
322                 LOG.error("Connectivity service not found in tapi context");
323                 return null;
324             }
325             return optConnServ.orElseThrow();
326         } catch (InterruptedException | ExecutionException e) {
327             LOG.error("Connectivity service not found in tapi context. Error:", e);
328             return null;
329         }
330     }
331
332     private void deleteConnectivityService(Uuid suuid) {
333         // First read connectivity service with service uuid and update info
334         InstanceIdentifier<ConnectivityService> connectivityServIID =
335             InstanceIdentifier.builder(Context.class).augmentation(Context1.class)
336                 .child(org.opendaylight.yang.gen.v1.urn
337                     .onf.otcc.yang.tapi.connectivity.rev221121.context.ConnectivityContext.class)
338                 .child(ConnectivityService.class, new ConnectivityServiceKey(suuid))
339                 .build();
340         try {
341             this.networkTransactionService.delete(LogicalDatastoreType.OPERATIONAL, connectivityServIID);
342             this.networkTransactionService.commit().get();
343         } catch (InterruptedException | ExecutionException e) {
344             LOG.error("Failed to delete TAPI connectivity service", e);
345         }
346     }
347
348     private void deleteConnection(Uuid connectionUuid) {
349         // First read connectivity service with service uuid and update info
350         InstanceIdentifier<org.opendaylight.yang.gen.v1
351                 .urn.onf.otcc.yang.tapi.connectivity.rev221121.connectivity.context.Connection> connectionIID =
352             InstanceIdentifier.builder(Context.class).augmentation(Context1.class)
353                 .child(org.opendaylight.yang.gen.v1.urn
354                     .onf.otcc.yang.tapi.connectivity.rev221121.context.ConnectivityContext.class)
355                 .child(org.opendaylight.yang.gen.v1.urn
356                         .onf.otcc.yang.tapi.connectivity.rev221121.connectivity.context.Connection.class,
357                     new org.opendaylight.yang.gen.v1.urn
358                             .onf.otcc.yang.tapi.connectivity.rev221121.connectivity.context.ConnectionKey(
359                         connectionUuid))
360                 .build();
361         try {
362             this.networkTransactionService.delete(LogicalDatastoreType.OPERATIONAL, connectionIID);
363             this.networkTransactionService.commit().get();
364         } catch (InterruptedException | ExecutionException e) {
365             LOG.error("Failed to delete TAPI connection", e);
366         }
367     }
368
369     public void setInput(CreateConnectivityServiceInput input) {
370         this.input = input;
371     }
372
373     public void setServiceUuid(Uuid serviceUuid) {
374         this.serviceUuid = serviceUuid;
375     }
376 }