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