Migration to TAPI 2.4 Step1
[transportpce.git] / tapi / src / main / java / org / opendaylight / transportpce / tapi / listeners / TapiRendererNotificationHandler.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.StandardCharsets;
12 import java.time.OffsetDateTime;
13 import java.time.ZoneOffset;
14 import java.time.format.DateTimeFormatter;
15 import java.util.HashMap;
16 import java.util.Map;
17 import java.util.Optional;
18 import java.util.Set;
19 import java.util.UUID;
20 import java.util.concurrent.ExecutionException;
21 import org.opendaylight.mdsal.binding.api.DataBroker;
22 import org.opendaylight.mdsal.binding.api.NotificationPublishService;
23 import org.opendaylight.mdsal.binding.api.NotificationService.CompositeListener;
24 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
25 import org.opendaylight.transportpce.common.network.NetworkTransactionImpl;
26 import org.opendaylight.transportpce.common.network.NetworkTransactionService;
27 import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.renderer.rev210915.RendererRpcResultSp;
28 import org.opendaylight.yang.gen.v1.nbi.notifications.rev230728.PublishTapiNotificationService;
29 import org.opendaylight.yang.gen.v1.nbi.notifications.rev230728.PublishTapiNotificationServiceBuilder;
30 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.common.rev221121.AdministrativeState;
31 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.common.rev221121.Context;
32 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.common.rev221121.DateAndTime;
33 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.common.rev221121.LifecycleState;
34 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.common.rev221121.OBJECTTYPETAPICONTEXT;
35 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.common.rev221121.OperationalState;
36 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.common.rev221121.Uuid;
37 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.common.rev221121.global._class.Name;
38 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.common.rev221121.global._class.NameKey;
39 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.Context1;
40 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.connectivity.context.ConnectionBuilder;
41 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.connectivity.context.ConnectionKey;
42 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.connectivity.context.ConnectivityService;
43 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.connectivity.context.ConnectivityServiceBuilder;
44 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.connectivity.context.ConnectivityServiceKey;
45 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.connectivity.service.Connection;
46 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.notification.rev221121.NOTIFICATIONTYPEATTRIBUTEVALUECHANGE;
47 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.notification.rev221121.notification.ChangedAttributes;
48 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.notification.rev221121.notification.ChangedAttributesBuilder;
49 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.notification.rev221121.notification.ChangedAttributesKey;
50 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.notification.rev221121.notification.TargetObjectName;
51 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.notification.rev221121.notification.TargetObjectNameBuilder;
52 import org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.notification.rev221121.notification.TargetObjectNameKey;
53 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57 public class TapiRendererNotificationHandler {
58
59     private static final Logger LOG = LoggerFactory.getLogger(TapiRendererNotificationHandler.class);
60     private final DataBroker dataBroker;
61     private Uuid serviceUuid;
62     private RendererRpcResultSp serviceRpcResultSp;
63     private final NetworkTransactionService networkTransactionService;
64     private final NotificationPublishService notificationPublishService;
65
66     public TapiRendererNotificationHandler(DataBroker dataBroker,
67             NotificationPublishService notificationPublishService) {
68         this.dataBroker = dataBroker;
69         this.networkTransactionService = new NetworkTransactionImpl(this.dataBroker);
70         this.notificationPublishService = notificationPublishService;
71     }
72
73     public CompositeListener getCompositeListener() {
74         return new CompositeListener(Set.of(
75             new CompositeListener.Component<>(RendererRpcResultSp.class, this::onRendererRpcResultSp)));
76     }
77
78     private void onRendererRpcResultSp(RendererRpcResultSp notification) {
79         if (compareServiceRpcResultSp(notification)) {
80             LOG.warn("ServiceRpcResultSp already wired !");
81             return;
82         }
83         serviceRpcResultSp = notification;
84         int notifType = serviceRpcResultSp.getNotificationType().getIntValue();
85         LOG.info("Renderer '{}' Notification received : {}", serviceRpcResultSp.getNotificationType().getName(),
86                 notification);
87         /* service-implementation-request. */
88         if (notifType == 3) {
89             onServiceImplementationResult(notification);
90         }
91     }
92
93     /**
94      * Process service implementation result for serviceName.
95      * @param notification RendererRpcResultSp
96      */
97     private void onServiceImplementationResult(RendererRpcResultSp notification) {
98         switch (serviceRpcResultSp.getStatus()) {
99             case Successful:
100                 if (this.serviceUuid != null) {
101                     onSuccededServiceImplementation();
102                 }
103                 break;
104             case Failed:
105                 onFailedServiceImplementation(notification.getServiceName());
106                 break;
107             case  Pending:
108                 LOG.warn("Service Implementation still pending according to RpcStatusEx");
109                 break;
110             default:
111                 LOG.warn("Service Implementation has an unknown RpcStatusEx code");
112                 break;
113         }
114     }
115
116     /**
117      * Process succeeded service implementation for service.
118      */
119     private void onSuccededServiceImplementation() {
120         LOG.info("Service implemented !");
121         // TODO: update Connections and Connectivity Service states
122         ConnectivityService connectivityService = getConnectivityService(this.serviceUuid);
123         if (connectivityService == null) {
124             LOG.error("Couldnt retrieve service from datastore");
125             return;
126         }
127         LOG.info("Connectivity service = {}", connectivityService);
128         // TODO --> this throws error because the renderer goes really fast. Is this normal??
129         ConnectivityService updtConnServ = new ConnectivityServiceBuilder(connectivityService)
130             .setAdministrativeState(AdministrativeState.UNLOCKED)
131             .setLifecycleState(LifecycleState.INSTALLED)
132             .setOperationalState(OperationalState.ENABLED)
133             .build();
134         for (Connection connection:updtConnServ.nonnullConnection().values()) {
135             updateConnectionState(connection.getConnectionUuid());
136         }
137         updateConnectivityService(updtConnServ);
138         // TODO: need to send notification to kafka in case the topic exists!!
139         sendNbiNotification(createNbiNotification(updtConnServ));
140     }
141
142     /**
143      * Process failed service implementation for serviceName.
144      * @param serviceName String
145      */
146     private void onFailedServiceImplementation(String serviceName) {
147         LOG.error("Renderer implementation failed !");
148         LOG.info("PCE cancel resource done OK !");
149         Uuid suuid = new Uuid(UUID.nameUUIDFromBytes(serviceName.getBytes(StandardCharsets.UTF_8))
150                 .toString());
151         // get connections of connectivity service and remove them from tapi context and then remove
152         //  service from context. The CEPs are maintained as they could be reused by another service
153         ConnectivityService connService = getConnectivityService(suuid);
154         if (connService == null) {
155             LOG.error("Service doesnt exist in tapi context");
156             return;
157         }
158         for (Connection connection:connService.getConnection().values()) {
159             deleteConnection(connection.getConnectionUuid());
160         }
161         deleteConnectivityService(suuid);
162     }
163
164     @SuppressFBWarnings(
165             value = "ES_COMPARING_STRINGS_WITH_EQ",
166             justification = "false positives, not strings but real object references comparisons")
167     private Boolean compareServiceRpcResultSp(RendererRpcResultSp notification) {
168         if (serviceRpcResultSp == null) {
169             return false;
170         }
171         if (serviceRpcResultSp.getNotificationType() != notification.getNotificationType()) {
172             return false;
173         }
174         if (serviceRpcResultSp.getServiceName() != notification.getServiceName()) {
175             return false;
176         }
177         if (serviceRpcResultSp.getStatus() != notification.getStatus()) {
178             return false;
179         }
180         if (serviceRpcResultSp.getStatusMessage() != notification.getStatusMessage()) {
181             return false;
182         }
183         return true;
184     }
185
186     private ConnectivityService getConnectivityService(Uuid suuid) {
187         // TODO: verify this is correct. Should we identify the context IID with the context UUID??
188         try {
189             // First read connectivity service with service uuid and update info
190             InstanceIdentifier<ConnectivityService> connectivityServIID =
191                 InstanceIdentifier.builder(Context.class).augmentation(Context1.class)
192                     .child(org.opendaylight.yang.gen.v1.urn
193                         .onf.otcc.yang.tapi.connectivity.rev221121.context.ConnectivityContext.class)
194                     .child(ConnectivityService.class, new ConnectivityServiceKey(suuid))
195                     .build();
196
197             Optional<ConnectivityService> optConnServ =
198                 this.networkTransactionService.read(LogicalDatastoreType.OPERATIONAL, connectivityServIID).get();
199             if (!optConnServ.isPresent()) {
200                 LOG.error("Connectivity service not found in tapi context");
201                 return null;
202             }
203             return optConnServ.orElseThrow();
204         } catch (InterruptedException | ExecutionException e) {
205             LOG.error("Failed to merge TAPI connectivity", e);
206             return null;
207         }
208     }
209
210     private void updateConnectionState(Uuid connectionUuid) {
211         // TODO: verify this is correct. Should we identify the context IID with the context UUID??
212         try {
213             // First read connection with connection uuid and update info
214             InstanceIdentifier<org.opendaylight.yang.gen.v1.urn
215                 .onf.otcc.yang.tapi.connectivity.rev221121.connectivity.context.Connection> connectionIID =
216                 InstanceIdentifier.builder(Context.class).augmentation(Context1.class)
217                     .child(org.opendaylight.yang.gen.v1.urn
218                         .onf.otcc.yang.tapi.connectivity.rev221121.context.ConnectivityContext.class)
219                     .child(org.opendaylight.yang.gen.v1.urn
220                             .onf.otcc.yang.tapi.connectivity.rev221121.connectivity.context.Connection.class,
221                         new ConnectionKey(connectionUuid))
222                     .build();
223
224             Optional<org.opendaylight.yang.gen.v1.urn
225                 .onf.otcc.yang.tapi.connectivity.rev221121.connectivity.context.Connection> optConn =
226                 this.networkTransactionService.read(LogicalDatastoreType.OPERATIONAL, connectionIID).get();
227             if (!optConn.isPresent()) {
228                 LOG.error("Connection not found in tapi context");
229                 return;
230             }
231             org.opendaylight.yang.gen.v1.urn.onf.otcc.yang.tapi.connectivity.rev221121.connectivity.context.Connection
232                 newConnection = new ConnectionBuilder(optConn.orElseThrow()).setLifecycleState(LifecycleState.INSTALLED)
233                     .setOperationalState(OperationalState.ENABLED).build();
234             // merge in datastore
235             this.networkTransactionService.merge(LogicalDatastoreType.OPERATIONAL, connectionIID,
236                     newConnection);
237             this.networkTransactionService.commit().get();
238             LOG.info("TAPI connection merged successfully.");
239         } catch (InterruptedException | ExecutionException e) {
240             LOG.error("Failed to merge TAPI connection", e);
241         }
242     }
243
244     private void updateConnectivityService(ConnectivityService updtConnServ) {
245         // TODO: verify this is correct. Should we identify the context IID with the context UUID??
246         try {
247             // First read connectivity service with connectivity service uuid and update info
248             InstanceIdentifier<ConnectivityService> connServIID =
249                 InstanceIdentifier.builder(Context.class).augmentation(Context1.class)
250                     .child(org.opendaylight.yang.gen.v1.urn
251                         .onf.otcc.yang.tapi.connectivity.rev221121.context.ConnectivityContext.class)
252                     .child(ConnectivityService.class, new ConnectivityServiceKey(updtConnServ.getUuid()))
253                     .build();
254
255             Optional<ConnectivityService> optConnServ =
256                 this.networkTransactionService.read(LogicalDatastoreType.OPERATIONAL, connServIID).get();
257             if (!optConnServ.isPresent()) {
258                 LOG.error("Connection not found in tapi context");
259                 return;
260             }
261             ConnectivityService newConnServ = new ConnectivityServiceBuilder(updtConnServ).build();
262             // merge in datastore
263             this.networkTransactionService.merge(LogicalDatastoreType.OPERATIONAL, connServIID,
264                     newConnServ);
265             this.networkTransactionService.commit().get();
266             LOG.info("TAPI connectivity service merged successfully.");
267         } catch (InterruptedException | ExecutionException e) {
268             LOG.error("Failed to merge TAPI connectivity service", e);
269         }
270     }
271
272     private void deleteConnectivityService(Uuid suuid) {
273         // First read connectivity service with service uuid and update info
274         InstanceIdentifier<ConnectivityService> connectivityServIID =
275             InstanceIdentifier.builder(Context.class).augmentation(Context1.class)
276                 .child(org.opendaylight.yang.gen.v1.urn
277                     .onf.otcc.yang.tapi.connectivity.rev221121.context.ConnectivityContext.class)
278                 .child(ConnectivityService.class, new ConnectivityServiceKey(suuid))
279                 .build();
280         try {
281             this.networkTransactionService.delete(LogicalDatastoreType.OPERATIONAL, connectivityServIID);
282             this.networkTransactionService.commit().get();
283         } catch (InterruptedException | ExecutionException e) {
284             LOG.error("Failed to delete TAPI connectivity service", e);
285         }
286     }
287
288     private void deleteConnection(Uuid connectionUuid) {
289         // First read connectivity service with service uuid and update info
290         InstanceIdentifier<org.opendaylight.yang.gen.v1
291             .urn.onf.otcc.yang.tapi.connectivity.rev221121.connectivity.context.Connection> connectionIID =
292             InstanceIdentifier.builder(Context.class).augmentation(Context1.class)
293                 .child(org.opendaylight.yang.gen.v1.urn
294                     .onf.otcc.yang.tapi.connectivity.rev221121.context.ConnectivityContext.class)
295                 .child(org.opendaylight.yang.gen.v1.urn
296                         .onf.otcc.yang.tapi.connectivity.rev221121.connectivity.context.Connection.class,
297                     new org.opendaylight.yang.gen.v1.urn
298                         .onf.otcc.yang.tapi.connectivity.rev221121.connectivity.context.ConnectionKey(
299                             connectionUuid))
300                 .build();
301         try {
302             this.networkTransactionService.delete(LogicalDatastoreType.OPERATIONAL, connectionIID);
303             this.networkTransactionService.commit().get();
304         } catch (InterruptedException | ExecutionException e) {
305             LOG.error("Failed to delete TAPI connection", e);
306         }
307     }
308
309     private void sendNbiNotification(PublishTapiNotificationService service) {
310         try {
311             this.notificationPublishService.putNotification(service);
312         } catch (InterruptedException e) {
313             LOG.warn("Cannot send notification to nbi", e);
314             Thread.currentThread().interrupt();
315         }
316     }
317
318     private PublishTapiNotificationService createNbiNotification(ConnectivityService connService) {
319         if (connService == null) {
320             LOG.error("ConnService is null");
321             return null;
322         }
323         /*
324         Map<ChangedAttributesKey, ChangedAttributes> changedStates = changedAttributesMap.entrySet()
325                 .stream()
326                 .filter(e -> e.getKey().getValueName().equals("administrative")
327                         || e.getKey().getValueName().equals("operational"))
328                 .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
329
330          */
331         Map<ChangedAttributesKey, ChangedAttributes> changedStates = new HashMap<>();
332         changedStates.put(new ChangedAttributesKey("administrativeState"),
333             new ChangedAttributesBuilder()
334                 .setNewValue(connService.getAdministrativeState().getName())
335                 .setOldValue(AdministrativeState.LOCKED.getName())
336                 .setValueName("administrativeState").build());
337         changedStates.put(new ChangedAttributesKey("operationalState"),
338             new ChangedAttributesBuilder()
339                 .setNewValue(connService.getOperationalState().getName())
340                 .setOldValue(OperationalState.DISABLED.getName())
341                 .setValueName("operationalState").build());
342         DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssxxx");
343         OffsetDateTime offsetDateTime = OffsetDateTime.now(ZoneOffset.UTC);
344         DateAndTime datetime = new DateAndTime(dtf.format(offsetDateTime));
345         Map<TargetObjectNameKey, TargetObjectName> targetObjectNames = new HashMap<>();
346         if (connService.getName() != null) {
347             for (Map.Entry<NameKey, Name> entry : connService.getName().entrySet()) {
348                 targetObjectNames.put(new TargetObjectNameKey(entry.getKey().getValueName()),
349                     new TargetObjectNameBuilder()
350                         .setValueName(entry.getValue().getValueName())
351                         .setValue(entry.getValue().getValue())
352                         .build());
353             }
354         }
355
356         return new PublishTapiNotificationServiceBuilder()
357             .setUuid(new Uuid(UUID.randomUUID().toString()))
358             .setTopic(connService.getUuid().getValue())
359             .setTargetObjectIdentifier(connService.getUuid())
360             .setNotificationType(NOTIFICATIONTYPEATTRIBUTEVALUECHANGE.VALUE)
361             .setChangedAttributes(changedStates)
362             .setEventTimeStamp(datetime)
363             .setTargetObjectName(targetObjectNames)
364             .setTargetObjectType(OBJECTTYPETAPICONTEXT.VALUE)
365             .setLayerProtocolName(connService.getLayerProtocolName())
366             .build();
367     }
368
369     public void setServiceUuid(Uuid serviceUuid) {
370         this.serviceUuid = serviceUuid;
371     }
372 }