fcb4bf183e8f0961fc4e894a23946c177386c912
[transportpce.git] / servicehandler / src / main / java / org / opendaylight / transportpce / servicehandler / listeners / ServiceListener.java
1 /*
2  * Copyright © 2021 Orange 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.servicehandler.listeners;
9
10 import com.google.common.util.concurrent.ListenableFuture;
11 import com.google.common.util.concurrent.MoreExecutors;
12 import java.util.Collection;
13 import java.util.HashMap;
14 import java.util.Map;
15 import java.util.Optional;
16 import java.util.concurrent.ExecutionException;
17 import java.util.concurrent.ScheduledExecutorService;
18 import java.util.concurrent.ScheduledThreadPoolExecutor;
19 import java.util.concurrent.TimeUnit;
20 import org.opendaylight.mdsal.binding.api.DataObjectModification;
21 import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
22 import org.opendaylight.mdsal.binding.api.DataTreeModification;
23 import org.opendaylight.mdsal.binding.api.NotificationPublishService;
24 import org.opendaylight.transportpce.common.ResponseCodes;
25 import org.opendaylight.transportpce.servicehandler.ServiceInput;
26 import org.opendaylight.transportpce.servicehandler.service.ServiceDataStoreOperations;
27 import org.opendaylight.yang.gen.v1.http.org.openroadm.common.service.types.rev230526.Restorable;
28 import org.opendaylight.yang.gen.v1.http.org.openroadm.common.service.types.rev230526.RpcActions;
29 import org.opendaylight.yang.gen.v1.http.org.openroadm.common.service.types.rev230526.routing.metric.RoutingMetric;
30 import org.opendaylight.yang.gen.v1.http.org.openroadm.common.service.types.rev230526.sdnc.request.header.SdncRequestHeaderBuilder;
31 import org.opendaylight.yang.gen.v1.http.org.openroadm.common.service.types.rev230526.service.resiliency.ServiceResiliency;
32 import org.opendaylight.yang.gen.v1.http.org.openroadm.common.state.types.rev191129.State;
33 import org.opendaylight.yang.gen.v1.http.org.openroadm.equipment.states.types.rev191129.AdminStates;
34 import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev230526.OrgOpenroadmServiceService;
35 import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev230526.ServiceCreateInputBuilder;
36 import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev230526.ServiceCreateOutput;
37 import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev230526.ServiceDeleteInputBuilder;
38 import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev230526.ServiceDeleteOutput;
39 import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev230526.ServiceRerouteInput;
40 import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev230526.ServiceRerouteInputBuilder;
41 import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev230526.ServiceRerouteOutput;
42 import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev230526.service.create.input.ServiceAEndBuilder;
43 import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev230526.service.create.input.ServiceZEndBuilder;
44 import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev230526.service.delete.input.ServiceDeleteReqInfo;
45 import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev230526.service.delete.input.ServiceDeleteReqInfoBuilder;
46 import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev230526.service.list.Services;
47 import org.opendaylight.yang.gen.v1.nbi.notifications.rev230726.PublishNotificationAlarmService;
48 import org.opendaylight.yang.gen.v1.nbi.notifications.rev230726.PublishNotificationAlarmServiceBuilder;
49 import org.opendaylight.yangtools.yang.common.RpcResult;
50 import org.osgi.service.component.annotations.Activate;
51 import org.osgi.service.component.annotations.Component;
52 import org.osgi.service.component.annotations.Reference;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 @Component
57 public class ServiceListener implements DataTreeChangeListener<Services> {
58
59     private static final Logger LOG = LoggerFactory.getLogger(ServiceListener.class);
60     private static final String PUBLISHER = "ServiceListener";
61     private OrgOpenroadmServiceService servicehandlerImpl;
62     private ServiceDataStoreOperations serviceDataStoreOperations;
63     private NotificationPublishService notificationPublishService;
64     private Map<String, ServiceInput> mapServiceInputReroute;
65     private final ScheduledExecutorService executor;
66
67     @Activate
68     public ServiceListener(@Reference OrgOpenroadmServiceService servicehandlerImpl,
69             @Reference ServiceDataStoreOperations serviceDataStoreOperations,
70             @Reference NotificationPublishService notificationPublishService) {
71         this.servicehandlerImpl = servicehandlerImpl;
72         this.notificationPublishService = notificationPublishService;
73         this.serviceDataStoreOperations = serviceDataStoreOperations;
74         this.executor = MoreExecutors.getExitingScheduledExecutorService(new ScheduledThreadPoolExecutor(4));
75         mapServiceInputReroute = new HashMap<>();
76     }
77
78     @Override
79     public void onDataTreeChanged(Collection<DataTreeModification<Services>> changes) {
80         LOG.info("onDataTreeChanged - {}", this.getClass().getSimpleName());
81         for (DataTreeModification<Services> change : changes) {
82             DataObjectModification<Services> rootService = change.getRootNode();
83             if (rootService.getDataBefore() == null) {
84                 continue;
85             }
86             String serviceInputName = rootService.getDataBefore().key().getServiceName();
87             switch (rootService.getModificationType()) {
88                 case DELETE:
89                     LOG.info("Service {} correctly deleted from controller", serviceInputName);
90                     if (mapServiceInputReroute.get(serviceInputName) != null) {
91                         serviceRerouteStep2(serviceInputName);
92                     }
93                     break;
94                 case WRITE:
95                     Services inputBefore = rootService.getDataBefore();
96                     Services inputAfter = rootService.getDataAfter();
97                     if (inputBefore.getOperationalState() == State.InService
98                             && inputAfter.getOperationalState() == State.OutOfService) {
99                         LOG.info("Service {} is becoming outOfService", serviceInputName);
100                         sendNbiNotification(new PublishNotificationAlarmServiceBuilder()
101                                 .setServiceName(inputAfter.getServiceName())
102                                 .setConnectionType(inputAfter.getConnectionType())
103                                 .setMessage("The service is now outOfService")
104                                 .setOperationalState(State.OutOfService)
105                                 .setPublisherName(PUBLISHER)
106                                 .build());
107                         if (inputAfter.getAdministrativeState() == AdminStates.InService
108                                 && inputAfter.getServiceResiliency() != null
109                                 && inputAfter.getServiceResiliency().getResiliency() != null
110                                 && inputAfter.getServiceResiliency().getResiliency().equals(Restorable.VALUE)) {
111                             LOG.info("Attempting to reroute the service '{}'...", serviceInputName);
112                             if (!serviceRerouteCheck(serviceInputName, inputAfter.getServiceResiliency(),
113                                     inputAfter.getRoutingMetric())) {
114                                 LOG.info("No other path available, cancelling reroute process of service '{}'...",
115                                         serviceInputName);
116                                 continue;
117                             }
118                             mapServiceInputReroute.put(serviceInputName, null);
119                             if (inputAfter.getServiceResiliency().getHoldoffTime() != null) {
120                                 LOG.info("Waiting hold off time before rerouting...");
121                                 executor.schedule(
122                                         () -> {
123                                             if (mapServiceInputReroute.containsKey(serviceInputName)
124                                                     && mapServiceInputReroute.get(serviceInputName) == null) {
125                                                 serviceRerouteStep1(serviceInputName);
126                                             } else {
127                                                 LOG.info("Cancelling reroute process of service '{}'...",
128                                                         serviceInputName);
129                                             }
130                                         },
131                                         Long.parseLong(String.valueOf(inputAfter.getServiceResiliency()
132                                                 .getHoldoffTime())),
133                                         TimeUnit.MILLISECONDS);
134                             } else {
135                                 serviceRerouteStep1(serviceInputName);
136                             }
137                         }
138                     } else if (inputAfter.getAdministrativeState() == AdminStates.InService
139                             && inputBefore.getOperationalState() == State.OutOfService
140                             && inputAfter.getOperationalState() == State.InService) {
141                         LOG.info("Service {} is becoming InService", serviceInputName);
142                         sendNbiNotification(new PublishNotificationAlarmServiceBuilder()
143                                 .setServiceName(inputAfter.getServiceName())
144                                 .setConnectionType(inputAfter.getConnectionType())
145                                 .setMessage("The service is now inService")
146                                 .setOperationalState(State.InService)
147                                 .setPublisherName(PUBLISHER)
148                                 .build());
149                         if (mapServiceInputReroute.containsKey(serviceInputName)
150                                 && mapServiceInputReroute.get(serviceInputName) == null) {
151                             mapServiceInputReroute.remove(serviceInputName);
152                         }
153                     }
154                     break;
155                 default:
156                     LOG.debug("Unknown modification type {}", rootService.getModificationType().name());
157                     break;
158             }
159         }
160     }
161
162     /**
163      * First step of the reroute : apply a service-delete RPC to the service.
164      *
165      * @param serviceNameToReroute Name of the service
166      */
167     private void serviceRerouteStep1(String serviceNameToReroute) {
168         mapServiceInputReroute.remove(serviceNameToReroute);
169         Optional<Services> serviceOpt = serviceDataStoreOperations.getService(serviceNameToReroute);
170         if (serviceOpt.isEmpty()) {
171             LOG.warn("Service '{}' does not exist in datastore", serviceNameToReroute);
172             return;
173         }
174         Services service = serviceOpt.orElseThrow();
175         ListenableFuture<RpcResult<ServiceDeleteOutput>> res = this.servicehandlerImpl.serviceDelete(
176                 new ServiceDeleteInputBuilder()
177                         .setSdncRequestHeader(new SdncRequestHeaderBuilder(service.getSdncRequestHeader())
178                                 .setRpcAction(RpcActions.ServiceDelete)
179                                 .build())
180                         .setServiceDeleteReqInfo(new ServiceDeleteReqInfoBuilder()
181                                 .setServiceName(serviceNameToReroute)
182                                 .setTailRetention(ServiceDeleteReqInfo.TailRetention.No)
183                                 .build())
184                         .build());
185         try {
186             String httpResponseCode = res.get().getResult().getConfigurationResponseCommon().getResponseCode();
187             if (httpResponseCode.equals(ResponseCodes.RESPONSE_OK)) {
188                 mapServiceInputReroute.put(serviceNameToReroute, new ServiceInput(
189                         new ServiceCreateInputBuilder()
190                                 .setServiceName(serviceNameToReroute)
191                                 .setCommonId(service.getCommonId())
192                                 .setConnectionType(service.getConnectionType())
193                                 .setServiceAEnd(new ServiceAEndBuilder(service.getServiceAEnd()).build())
194                                 .setServiceZEnd(new ServiceZEndBuilder(service.getServiceZEnd()).build())
195                                 .setHardConstraints(service.getHardConstraints())
196                                 .setSoftConstraints(service.getSoftConstraints())
197                                 .setSdncRequestHeader(service.getSdncRequestHeader())
198                                 .setCustomer(service.getCustomer())
199                                 .setCustomerContact(service.getCustomerContact())
200                                 .setServiceResiliency(service.getServiceResiliency())
201                                 .setDueDate(service.getDueDate())
202                                 .setOperatorContact(service.getOperatorContact())
203                                 .build()));
204                 LOG.info("ServiceRerouteStep1 (deletion of the service) in progress");
205             } else {
206                 LOG.warn("ServiceRerouteStep1 (deletion of the service) failed '{}' http code ", httpResponseCode);
207             }
208         } catch (ExecutionException | InterruptedException e) {
209             LOG.warn("ServiceRerouteStep1 FAILED ! ", e);
210         }
211     }
212
213     /**
214      * Second step of the reroute : apply a service-create RPC. This method is called after the first step of reroute
215      * when the service has been successfully deleted.
216      *
217      * @param serviceNameToReroute Name of the service
218      */
219     private void serviceRerouteStep2(String serviceNameToReroute) {
220         ListenableFuture<RpcResult<ServiceCreateOutput>> res = this.servicehandlerImpl.serviceCreate(
221                 mapServiceInputReroute.get(serviceNameToReroute).getServiceCreateInput());
222         try {
223             String httpResponseCode = res.get().getResult().getConfigurationResponseCommon().getResponseCode();
224             if (httpResponseCode.equals(ResponseCodes.RESPONSE_OK)) {
225                 LOG.info("ServiceRerouteStep2 (creation of the new service) in progress");
226             } else {
227                 LOG.warn("ServiceRerouteStep2 (creation of the new service) failed '{}' http code ", httpResponseCode);
228             }
229         } catch (ExecutionException | InterruptedException e) {
230             LOG.warn("ServiceRerouteStep2 FAILED ! ", e);
231         }
232         mapServiceInputReroute.remove(serviceNameToReroute);
233     }
234
235     /**
236      * Prior to the reroute steps: check that an alternative route of the service is possible.
237      *
238      * @param serviceNameToReroute Name of the service
239      * @param serviceResiliency Resiliency of the service
240      * @param routingMetric Metric of the routing
241      */
242     private boolean serviceRerouteCheck(String serviceNameToReroute, ServiceResiliency serviceResiliency,
243                                         RoutingMetric routingMetric) {
244         ServiceRerouteInput serviceRerouteInput = new ServiceRerouteInputBuilder()
245                 .setServiceName(serviceNameToReroute)
246                 .setServiceResiliency(serviceResiliency)
247                 .setRoutingMetric(routingMetric)
248                 .setSdncRequestHeader(new SdncRequestHeaderBuilder()
249                         .setRpcAction(RpcActions.ServiceReroute)
250                         .build())
251                 .build();
252         ListenableFuture<RpcResult<ServiceRerouteOutput>> res = this.servicehandlerImpl.serviceReroute(
253                 serviceRerouteInput);
254         try {
255             return res.get().getResult().getConfigurationResponseCommon().getResponseCode()
256                     .equals(ResponseCodes.RESPONSE_OK);
257         } catch (ExecutionException | InterruptedException e) {
258             LOG.warn("ServiceRerouteCheck FAILED ! ", e);
259             return false;
260         }
261     }
262
263     /**
264      * Send notification to NBI notification in order to publish message.
265      *
266      * @param service PublishNotificationAlarmService
267      */
268     private void sendNbiNotification(PublishNotificationAlarmService service) {
269         try {
270             notificationPublishService.putNotification(service);
271         } catch (InterruptedException e) {
272             LOG.warn("Cannot send notification to nbi", e);
273             Thread.currentThread().interrupt();
274         }
275     }
276 }