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