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