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