Implement an autonomous service rerouting 85/101585/7
authorThierry Jiao <thierry.jiao@orange.com>
Tue, 24 May 2022 07:42:16 +0000 (09:42 +0200)
committerGuillaume Lambert <guillaume.lambert@orange.com>
Thu, 30 Jun 2022 07:06:53 +0000 (07:06 +0000)
If the OperationalState of a service becomes 'outOfService',
the service will be rerouted through the following process :

  - 1) Complete deletion of the service
  - 2) Recreation of the service with a new path

JIRA: TRNSPRTPCE-676
Signed-off-by: Thierry Jiao <thierry.jiao@orange.com>
Change-Id: I9227f0f2cf5993c54e66bffc0fbd4e3168a6839e

lighty/src/main/java/io/lighty/controllers/tpce/module/TransportPCEImpl.java
servicehandler/src/main/java/org/opendaylight/transportpce/servicehandler/listeners/ServiceListener.java
servicehandler/src/main/resources/OSGI-INF/blueprint/servicehandler-blueprint.xml
servicehandler/src/test/java/org/opendaylight/transportpce/servicehandler/listeners/ServiceListenerTest.java

index 37a766d4facab32349ebf0eb6eefaac2fdcf5f02..fb3a8f828100277b2932919a53d8751062bbe377 100644 (file)
@@ -198,13 +198,13 @@ public class TransportPCEImpl extends AbstractLightyModule implements TransportP
             lightyServices.getBindingNotificationPublishService(), networkModelService);
         PceListenerImpl pceListenerImpl = new PceListenerImpl(rendererServiceOperations, pathComputationService,
             lightyServices.getBindingNotificationPublishService(), serviceDataStoreOperations);
-        ServiceListener serviceListener = new ServiceListener(lightyServices.getBindingDataBroker(),
-                lightyServices.getBindingNotificationPublishService());
         NetworkModelListenerImpl networkModelListenerImpl = new NetworkModelListenerImpl(
                 lightyServices.getBindingNotificationPublishService(), serviceDataStoreOperations);
         ServicehandlerImpl servicehandler = new ServicehandlerImpl(lightyServices.getBindingDataBroker(),
             pathComputationService, rendererServiceOperations, lightyServices.getBindingNotificationPublishService(),
             pceListenerImpl, rendererListenerImpl, networkModelListenerImpl, serviceDataStoreOperations);
+        ServiceListener serviceListener = new ServiceListener(servicehandler, serviceDataStoreOperations,
+                lightyServices.getBindingNotificationPublishService());
         servicehandlerProvider = new ServicehandlerProvider(lightyServices.getBindingDataBroker(),
                 lightyServices.getRpcProviderService(), lightyServices.getNotificationService(),
                 serviceDataStoreOperations, pceListenerImpl, serviceListener, rendererListenerImpl,
index c4ee91ea902c5e90680c3e420b9501ae9fecd02a..73702010884f06c062614745e51950879bf21547 100644 (file)
@@ -7,16 +7,41 @@
  */
 package org.opendaylight.transportpce.servicehandler.listeners;
 
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
 import java.util.Collection;
-import org.opendaylight.mdsal.binding.api.DataBroker;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
 import org.opendaylight.mdsal.binding.api.DataObjectModification;
 import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
 import org.opendaylight.mdsal.binding.api.DataTreeModification;
 import org.opendaylight.mdsal.binding.api.NotificationPublishService;
+import org.opendaylight.transportpce.common.ResponseCodes;
+import org.opendaylight.transportpce.servicehandler.ServiceInput;
+import org.opendaylight.transportpce.servicehandler.impl.ServicehandlerImpl;
+import org.opendaylight.transportpce.servicehandler.service.ServiceDataStoreOperations;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.common.service.types.rev211210.Restorable;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.common.service.types.rev211210.RpcActions;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.common.service.types.rev211210.sdnc.request.header.SdncRequestHeaderBuilder;
 import org.opendaylight.yang.gen.v1.http.org.openroadm.common.state.types.rev191129.State;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.equipment.states.types.rev191129.AdminStates;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev211210.ServiceCreateInputBuilder;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev211210.ServiceCreateOutput;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev211210.ServiceDeleteInputBuilder;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev211210.ServiceDeleteOutput;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev211210.service.create.input.ServiceAEndBuilder;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev211210.service.create.input.ServiceZEndBuilder;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev211210.service.delete.input.ServiceDeleteReqInfo;
+import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev211210.service.delete.input.ServiceDeleteReqInfoBuilder;
 import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev211210.service.list.Services;
 import org.opendaylight.yang.gen.v1.nbi.notifications.rev210813.PublishNotificationAlarmService;
 import org.opendaylight.yang.gen.v1.nbi.notifications.rev210813.PublishNotificationAlarmServiceBuilder;
+import org.opendaylight.yangtools.yang.common.RpcResult;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -24,14 +49,22 @@ public class ServiceListener implements DataTreeChangeListener<Services> {
 
     private static final Logger LOG = LoggerFactory.getLogger(ServiceListener.class);
     private static final String PUBLISHER = "ServiceListener";
-    private final DataBroker dataBroker;
+    private ServicehandlerImpl servicehandlerImpl;
+    private ServiceDataStoreOperations serviceDataStoreOperations;
     private NotificationPublishService notificationPublishService;
+    private Map<String, ServiceInput> mapServiceInputReroute;
+    private final ScheduledExecutorService executor;
 
-    public ServiceListener(final DataBroker dataBroker, NotificationPublishService notificationPublishService) {
-        this.dataBroker = dataBroker;
+    public ServiceListener(ServicehandlerImpl servicehandlerImpl, ServiceDataStoreOperations serviceDataStoreOperations,
+                           NotificationPublishService notificationPublishService) {
+        this.servicehandlerImpl = servicehandlerImpl;
         this.notificationPublishService = notificationPublishService;
+        this.serviceDataStoreOperations = serviceDataStoreOperations;
+        this.executor = MoreExecutors.getExitingScheduledExecutorService(new ScheduledThreadPoolExecutor(4));
+        mapServiceInputReroute = new HashMap<>();
     }
 
+    @Override
     public void onDataTreeChanged(Collection<DataTreeModification<Services>> changes) {
         LOG.info("onDataTreeChanged - {}", this.getClass().getSimpleName());
         for (DataTreeModification<Services> change : changes) {
@@ -39,34 +72,67 @@ public class ServiceListener implements DataTreeChangeListener<Services> {
             if (rootService.getDataBefore() == null) {
                 continue;
             }
-            String serviceName = rootService.getDataBefore().key().getServiceName();
+            String serviceInputName = rootService.getDataBefore().key().getServiceName();
             switch (rootService.getModificationType()) {
                 case DELETE:
-                    LOG.info("Service {} correctly deleted from controller", serviceName);
+                    LOG.info("Service {} correctly deleted from controller", serviceInputName);
+                    if (mapServiceInputReroute.get(serviceInputName) != null) {
+                        serviceRerouteStep2(serviceInputName);
+                    }
                     break;
                 case WRITE:
-                    Services input = rootService.getDataAfter();
-                    if (rootService.getDataBefore().getOperationalState() == State.InService
-                            && rootService.getDataAfter().getOperationalState() == State.OutOfService) {
-                        LOG.info("Service {} is becoming outOfService", serviceName);
+                    Services inputBefore = rootService.getDataBefore();
+                    Services inputAfter = rootService.getDataAfter();
+                    if (inputBefore.getOperationalState() == State.InService
+                            && inputAfter.getOperationalState() == State.OutOfService) {
+                        LOG.info("Service {} is becoming outOfService", serviceInputName);
                         sendNbiNotification(new PublishNotificationAlarmServiceBuilder()
-                                .setServiceName(input.getServiceName())
-                                .setConnectionType(input.getConnectionType())
+                                .setServiceName(inputAfter.getServiceName())
+                                .setConnectionType(inputAfter.getConnectionType())
                                 .setMessage("The service is now outOfService")
                                 .setOperationalState(State.OutOfService)
                                 .setPublisherName(PUBLISHER)
                                 .build());
-                    }
-                    else if (rootService.getDataBefore().getOperationalState() == State.OutOfService
-                            && rootService.getDataAfter().getOperationalState() == State.InService) {
-                        LOG.info("Service {} is becoming InService", serviceName);
+                        if (inputAfter.getAdministrativeState() == AdminStates.InService
+                                && inputAfter.getServiceResiliency() != null
+                                && inputAfter.getServiceResiliency().getResiliency() != null
+                                && inputAfter.getServiceResiliency().getResiliency().equals(Restorable.class)) {
+                            LOG.info("Attempting to reroute the service '{}'...", serviceInputName);
+                            // It is used for hold off time purposes
+                            mapServiceInputReroute.put(serviceInputName, null);
+                            if (inputAfter.getServiceResiliency().getHoldoffTime() != null) {
+                                LOG.info("Waiting hold off time before rerouting...");
+                                executor.schedule(
+                                        () -> {
+                                            if (mapServiceInputReroute.containsKey(serviceInputName)
+                                                    && mapServiceInputReroute.get(serviceInputName) == null) {
+                                                serviceRerouteStep1(serviceInputName);
+                                            } else {
+                                                LOG.info("Cancelling rerouting for service '{}'...", serviceInputName);
+                                            }
+                                        },
+                                        Long.parseLong(String.valueOf(inputAfter.getServiceResiliency()
+                                                .getHoldoffTime())),
+                                        TimeUnit.MILLISECONDS);
+                            } else {
+                                serviceRerouteStep1(serviceInputName);
+                            }
+                        }
+                    } else if (inputAfter.getAdministrativeState() == AdminStates.InService
+                            && inputBefore.getOperationalState() == State.OutOfService
+                            && inputAfter.getOperationalState() == State.InService) {
+                        LOG.info("Service {} is becoming InService", serviceInputName);
                         sendNbiNotification(new PublishNotificationAlarmServiceBuilder()
-                                .setServiceName(input.getServiceName())
-                                .setConnectionType(input.getConnectionType())
+                                .setServiceName(inputAfter.getServiceName())
+                                .setConnectionType(inputAfter.getConnectionType())
                                 .setMessage("The service is now inService")
                                 .setOperationalState(State.InService)
                                 .setPublisherName(PUBLISHER)
                                 .build());
+                        if (mapServiceInputReroute.containsKey(serviceInputName)
+                                && mapServiceInputReroute.get(serviceInputName) == null) {
+                            mapServiceInputReroute.remove(serviceInputName);
+                        }
                     }
                     break;
                 default:
@@ -76,6 +142,79 @@ public class ServiceListener implements DataTreeChangeListener<Services> {
         }
     }
 
+    /**
+     * First step of the reroute : apply a service-delete RPC to the service.
+     *
+     * @param serviceNameToReroute Name of the service
+     */
+    private void serviceRerouteStep1(String serviceNameToReroute) {
+        mapServiceInputReroute.remove(serviceNameToReroute);
+        Optional<Services> serviceOpt = serviceDataStoreOperations.getService(serviceNameToReroute);
+        if (serviceOpt.isEmpty()) {
+            LOG.warn("Service '{}' does not exist in datastore", serviceNameToReroute);
+            return;
+        }
+        Services service = serviceOpt.get();
+        ListenableFuture<RpcResult<ServiceDeleteOutput>> res = this.servicehandlerImpl.serviceDelete(
+                new ServiceDeleteInputBuilder()
+                        .setSdncRequestHeader(new SdncRequestHeaderBuilder(service.getSdncRequestHeader())
+                                .setRpcAction(RpcActions.ServiceDelete)
+                                .build())
+                        .setServiceDeleteReqInfo(new ServiceDeleteReqInfoBuilder()
+                                .setServiceName(serviceNameToReroute)
+                                .setTailRetention(ServiceDeleteReqInfo.TailRetention.No)
+                                .build())
+                        .build());
+        try {
+            String httpResponseCode = res.get().getResult().getConfigurationResponseCommon().getResponseCode();
+            if (httpResponseCode.equals(ResponseCodes.RESPONSE_OK)) {
+                mapServiceInputReroute.put(serviceNameToReroute, new ServiceInput(
+                        new ServiceCreateInputBuilder()
+                                .setServiceName(serviceNameToReroute)
+                                .setCommonId(service.getCommonId())
+                                .setConnectionType(service.getConnectionType())
+                                .setServiceAEnd(new ServiceAEndBuilder(service.getServiceAEnd()).build())
+                                .setServiceZEnd(new ServiceZEndBuilder(service.getServiceZEnd()).build())
+                                .setHardConstraints(service.getHardConstraints())
+                                .setSoftConstraints(service.getSoftConstraints())
+                                .setSdncRequestHeader(service.getSdncRequestHeader())
+                                .setCustomer(service.getCustomer())
+                                .setCustomerContact(service.getCustomerContact())
+                                .setServiceResiliency(service.getServiceResiliency())
+                                .setDueDate(service.getDueDate())
+                                .setOperatorContact(service.getOperatorContact())
+                                .build()));
+                LOG.info("ServiceRerouteStep1 (deletion of the service) in progress");
+            } else {
+                LOG.warn("ServiceRerouteStep1 (deletion of the service) failed '{}' http code ", httpResponseCode);
+            }
+        } catch (ExecutionException | InterruptedException e) {
+            LOG.warn("ServiceRerouteStep1 FAILED ! ", e);
+        }
+    }
+
+    /**
+     * Second step of the reroute : apply a service-create RPC. This method is called after the first step of reroute
+     * when the service has been successfully deleted.
+     *
+     * @param serviceNameToReroute Name of the service
+     */
+    private void serviceRerouteStep2(String serviceNameToReroute) {
+        ListenableFuture<RpcResult<ServiceCreateOutput>> res = this.servicehandlerImpl.serviceCreate(
+                mapServiceInputReroute.get(serviceNameToReroute).getServiceCreateInput());
+        try {
+            String httpResponseCode = res.get().getResult().getConfigurationResponseCommon().getResponseCode();
+            if (httpResponseCode.equals(ResponseCodes.RESPONSE_OK)) {
+                LOG.info("ServiceRerouteStep2 (creation of the new service) in progress");
+            } else {
+                LOG.warn("ServiceRerouteStep2 (creation of the new service) failed '{}' http code ", httpResponseCode);
+            }
+        } catch (ExecutionException | InterruptedException e) {
+            LOG.warn("ServiceRerouteStep2 FAILED ! ", e);
+        }
+        mapServiceInputReroute.remove(serviceNameToReroute);
+    }
+
     /**
      * Send notification to NBI notification in order to publish message.
      *
index 5001049e868b30fb8f3800364de56d0d826180a2..affeddf0dd85cd0e7f949feb9c499fb93a02e69c 100644 (file)
@@ -44,7 +44,8 @@ Author: Martial Coulibaly <martial.coulibaly@gfi.com> on behalf of Orange
     </bean>
 
     <bean id="serviceListener" class="org.opendaylight.transportpce.servicehandler.listeners.ServiceListener">
-        <argument ref="dataBroker" />
+        <argument ref="serviceHandlerImpl" />
+        <argument ref="serviceDatastoreOperation" />
         <argument ref="notificationPublishService" />
     </bean>
 
index 5dfe23e79a718c83f2d38c409384be95c7d64102..a18f8da39a6beed57664d7ed06224e532ed4059d 100755 (executable)
@@ -23,10 +23,11 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
-import org.opendaylight.mdsal.binding.api.DataBroker;
 import org.opendaylight.mdsal.binding.api.DataObjectModification;
 import org.opendaylight.mdsal.binding.api.DataTreeModification;
 import org.opendaylight.mdsal.binding.api.NotificationPublishService;
+import org.opendaylight.transportpce.servicehandler.impl.ServicehandlerImpl;
+import org.opendaylight.transportpce.servicehandler.service.ServiceDataStoreOperations;
 import org.opendaylight.yang.gen.v1.http.org.openroadm.common.node.types.rev210528.NodeIdType;
 import org.opendaylight.yang.gen.v1.http.org.openroadm.common.service.types.rev211210.ConnectionType;
 import org.opendaylight.yang.gen.v1.http.org.openroadm.common.service.types.rev211210.service.ServiceAEnd;
@@ -55,7 +56,9 @@ import org.opendaylight.yangtools.yang.common.Uint8;
 public class ServiceListenerTest {
 
     @Mock
-    private DataBroker dataBroker;
+    private ServicehandlerImpl servicehandler;
+    @Mock
+    private ServiceDataStoreOperations serviceDataStoreOperations;
     @Mock
     private NotificationPublishService notificationPublishService;
 
@@ -70,7 +73,8 @@ public class ServiceListenerTest {
 
         when(service.getModificationType()).thenReturn(DataObjectModification.ModificationType.DELETE);
         when(service.getDataBefore()).thenReturn(buildService(State.InService, AdminStates.InService));
-        ServiceListener listener = new ServiceListener(dataBroker, notificationPublishService);
+        ServiceListener listener = new ServiceListener(servicehandler, serviceDataStoreOperations,
+                notificationPublishService);
         listener.onDataTreeChanged(changes);
         verify(ch, times(1)).getRootNode();
         verify(service, times(1)).getModificationType();
@@ -96,12 +100,13 @@ public class ServiceListenerTest {
         when(service.getModificationType()).thenReturn(DataObjectModification.ModificationType.WRITE);
         when(service.getDataBefore()).thenReturn(buildService(State.InService, AdminStates.InService));
         when(service.getDataAfter()).thenReturn(serviceDown);
-        ServiceListener listener = new ServiceListener(dataBroker, notificationPublishService);
+        ServiceListener listener = new ServiceListener(servicehandler, serviceDataStoreOperations,
+                notificationPublishService);
         listener.onDataTreeChanged(changes);
         verify(ch, times(1)).getRootNode();
         verify(service, times(1)).getModificationType();
         verify(service, times(3)).getDataBefore();
-        verify(service, times(2)).getDataAfter();
+        verify(service, times(1)).getDataAfter();
         PublishNotificationAlarmService publishNotificationAlarmService =
                 buildNotificationAlarmService(serviceDown, "The service is now outOfService");
         try {
@@ -123,7 +128,8 @@ public class ServiceListenerTest {
 
         when(service.getModificationType()).thenReturn(DataObjectModification.ModificationType.SUBTREE_MODIFIED);
         when(service.getDataBefore()).thenReturn(buildService(State.InService, AdminStates.InService));
-        ServiceListener listener = new ServiceListener(dataBroker, notificationPublishService);
+        ServiceListener listener = new ServiceListener(servicehandler, serviceDataStoreOperations,
+                notificationPublishService);
         listener.onDataTreeChanged(changes);
         verify(ch, times(1)).getRootNode();
         verify(service, times(2)).getModificationType();
@@ -139,13 +145,13 @@ public class ServiceListenerTest {
     private Services buildService(State state, AdminStates adminStates) {
         ServiceAEnd serviceAEnd = getServiceAEndBuild().build();
         ServiceZEnd serviceZEnd = new ServiceZEndBuilder()
-                    .setClli("clli")
-                    .setServiceFormat(ServiceFormat.OC)
-                    .setServiceRate(Uint32.valueOf(1))
-                    .setNodeId(new NodeIdType("XPONDER-3-2"))
-                    .setTxDirection(Map.of(new TxDirectionKey(getTxDirection().key()),getTxDirection()))
-                    .setRxDirection(Map.of(new RxDirectionKey(getRxDirection().key()), getRxDirection()))
-                    .build();
+                .setClli("clli")
+                .setServiceFormat(ServiceFormat.OC)
+                .setServiceRate(Uint32.valueOf(1))
+                .setNodeId(new NodeIdType("XPONDER-3-2"))
+                .setTxDirection(Map.of(new TxDirectionKey(getTxDirection().key()), getTxDirection()))
+                .setRxDirection(Map.of(new RxDirectionKey(getRxDirection().key()), getRxDirection()))
+                .build();
         ServicesBuilder builtInput = new ServicesBuilder()
                 .setCommonId("commonId")
                 .setConnectionType(ConnectionType.Service)
@@ -165,7 +171,7 @@ public class ServiceListenerTest {
                 .setServiceFormat(ServiceFormat.OC)
                 .setServiceRate(Uint32.valueOf(1))
                 .setNodeId(new NodeIdType("XPONDER-1-2"))
-                .setTxDirection(Map.of(new TxDirectionKey(getTxDirection().key()),getTxDirection()))
+                .setTxDirection(Map.of(new TxDirectionKey(getTxDirection().key()), getTxDirection()))
                 .setRxDirection(Map.of(new RxDirectionKey(getRxDirection().key()), getRxDirection()));
     }