Several bug fixes and Python SFF/SF reference implementation 43/12843/1
authorReinaldo Penno <[email protected]>
Fri, 14 Nov 2014 12:35:36 +0000 (02:35 -1000)
committerReinaldo Penno <[email protected]>
Fri, 14 Nov 2014 12:35:36 +0000 (02:35 -1000)
Introduced a CheckedFuture callback implementation
Started using CheckedFuture for some transactions
Fixed a bug with SB REST where the URI was not extracted from model correctly
Fixed handling of exceptions in SB REST code
Added LOGs to several transaction
Added a new directory and Python reference implementations of SFF/SF
Fixed minor bugs with SFC python regression

Change-Id: I56811650900eb44818ae144368969bb01a8d5274
Signed-off-by: Reinaldo Penno <[email protected]>
14 files changed:
sfc-provider/src/main/java/org/opendaylight/sfc/provider/OpendaylightSfc.java
sfc-provider/src/main/java/org/opendaylight/sfc/provider/SfcProviderAbstractRestAPI.java
sfc-provider/src/main/java/org/opendaylight/sfc/provider/SfcProviderRestAPI.java
sfc-provider/src/main/java/org/opendaylight/sfc/provider/api/SfcDataStoreCallback.java [new file with mode: 0644]
sfc-provider/src/main/java/org/opendaylight/sfc/provider/api/SfcProviderServicePathAPI.java
sfc-provider/src/main/java/org/opendaylight/sfc/provider/api/SfcProviderServiceTypeAPI.java
sfc-py/.gitignore [new file with mode: 0644]
sfc-py/odl2ovs.py [new file with mode: 0644]
sfc-py/odl2ovs_cli.py [new file with mode: 0644]
sfc-py/pysf_oldnsh.py [new file with mode: 0644]
sfc-py/service_function.py [new file with mode: 0644]
sfc-py/sff_new.py [new file with mode: 0644]
sfc-test/sfc_dcloud_messages.py
sfc-test/sfc_dcloud_regression.py

index ae3dfe8f32f8279f6a5ec9967e72d9a2767eafa0..46ac31513883d5af959f12d35261c0fd0a119f48 100755 (executable)
@@ -75,7 +75,7 @@ public class OpendaylightSfc implements AutoCloseable {
 
     public OpendaylightSfc() {
 
-       executor = Executors.newFixedThreadPool(6);
+       executor = Executors.newFixedThreadPool(10);
        opendaylightSfcObj = this;
     }
 
index cd12bf86b6d7f9b05bc064ec72d92434162aedd7..4d02968daf6a860012798f751fea359be0ca4cd7 100755 (executable)
@@ -82,9 +82,12 @@ abstract public class SfcProviderAbstractRestAPI implements Runnable {
             try {
                 method = c.getDeclaredMethod(methodName, parameterTypes);
                 method.invoke(this, parameters);
-            } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
+            } catch (IllegalAccessException | NoSuchMethodException e) {
                 LOG.error("Could not find method {} in class", methodName);
                 return;
+            } catch (InvocationTargetException e) {
+                LOG.error("Invocation target exception: {}", e.getMessage());
+                return;
             } catch (UniformInterfaceException e) {
                 LOG.error("REST Server error. Message: {}",
                         e.getMessage());
index e7be5bca060445b068b1d6482e8cf157c2491b5a..a1342ae128439ea81c23ee2953244aa55e81ddd0 100755 (executable)
@@ -91,7 +91,7 @@ public class SfcProviderRestAPI extends SfcProviderAbstractRestAPI {
         */
 
         String sffJSON = getRESTObj(getServiceFunctionForwarderURI(serviceFunctionForwarder));
-        String restURI = serviceFunctionForwarder.getRestUri().toString();
+        String restURI = serviceFunctionForwarder.getRestUri().getValue();
         //restURI = "http://127.0.0.1:5000";
 
         sffURI = restURI + "/config/service" +
@@ -223,7 +223,7 @@ public class SfcProviderRestAPI extends SfcProviderAbstractRestAPI {
                     ServiceFunctionForwarder serviceFunctionForwarder =
                             (ServiceFunctionForwarder) future
                                     .get();
-                    restURI = serviceFunctionForwarder.getRestUri().toString();
+                    restURI = serviceFunctionForwarder.getRestUri().getValue();
                     // Testing
                     //restURI = "http://127.0.0.1:5000";
 
diff --git a/sfc-provider/src/main/java/org/opendaylight/sfc/provider/api/SfcDataStoreCallback.java b/sfc-provider/src/main/java/org/opendaylight/sfc/provider/api/SfcDataStoreCallback.java
new file mode 100644 (file)
index 0000000..a23fe42
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.sfc.provider.api;
+
+import com.google.common.util.concurrent.FutureCallback;
+import org.opendaylight.controller.md.sal.common.api.data.OptimisticLockFailedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class implements the general Datastore checked future
+ * callback
+ * <p/>
+ *
+ * @author Reinaldo Penno ([email protected])
+ * @version 0.1
+ * <p/>
+ * @since       2014-06-30
+ */
+public class SfcDataStoreCallback implements FutureCallback<Void>
+{
+    private static final Logger LOG = LoggerFactory.getLogger(SfcDataStoreCallback.class);
+    private boolean transaction_successful;
+
+    public boolean getTransactioSuccessful() {return transaction_successful; }
+
+    @Override
+    public void onSuccess(final Void result)
+    {
+        // Commited successfully
+        this.transaction_successful = true;
+    }
+
+    @Override
+    public void onFailure(final Throwable t)
+    {
+        // Transaction failed
+        this.transaction_successful = false;
+
+        if (t instanceof OptimisticLockFailedException)
+        {
+            // Failed because of concurrent transaction modifying same data
+        } else
+        {
+            // Some other type of TransactionCommitFailedException
+        }
+    }
+}
index dc463475fe29be331295cd28729cb946ddcb1060..852e0b6f7f249d943ac6b25ec302fcc2de8d0504 100755 (executable)
@@ -9,50 +9,31 @@
 package org.opendaylight.sfc.provider.api;
 
 import com.google.common.base.Optional;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
 import org.opendaylight.sfc.provider.SfcProviderRestAPI;
-import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sf
-        .rev140701.ServiceFunctionsState;
-import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sf
-        .rev140701.service.functions.ServiceFunction;
-import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sf
-        .rev140701.service.functions.state.ServiceFunctionState;
-import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sf
-        .rev140701.service.functions.state.ServiceFunctionStateKey;
-import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfc
-        .rev140701.ServiceFunctionChainsState;
-import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfc
-        .rev140701.service.function.chain.grouping.ServiceFunctionChain;
-import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfc
-        .rev140701.service.function.chain.grouping.service.function.chain
-        .SfcServiceFunction;
-import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfc
-        .rev140701.service.function.chains.state.ServiceFunctionChainState;
-import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfc
-        .rev140701.service.function.chains.state
-        .ServiceFunctionChainStateBuilder;
-import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfc
-        .rev140701.service.function.chains.state.ServiceFunctionChainStateKey;
-import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp
-        .rev140701.ServiceFunctionPaths;
-import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp
-        .rev140701.service.function.paths.ServiceFunctionPath;
-import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp
-        .rev140701.service.function.paths.ServiceFunctionPathBuilder;
-import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp
-        .rev140701.service.function.paths.ServiceFunctionPathKey;
-import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp
-        .rev140701.service.function.paths.service.function.path.ServicePathHop;
-import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp
-        .rev140701.service.function.paths.service.function.path
-        .ServicePathHopBuilder;
-import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sft
-        .rev140701.service.function.types.ServiceFunctionType;
-import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sft
-        .rev140701.service.function.types.service.function.type
-        .SftServiceFunctionName;
+import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sf.rev140701.ServiceFunctionsState;
+import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sf.rev140701.service.functions.ServiceFunction;
+import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sf.rev140701.service.functions.state.ServiceFunctionState;
+import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sf.rev140701.service.functions.state.ServiceFunctionStateKey;
+import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfc.rev140701.ServiceFunctionChainsState;
+import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfc.rev140701.service.function.chain.grouping.ServiceFunctionChain;
+import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfc.rev140701.service.function.chain.grouping.service.function.chain.SfcServiceFunction;
+import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfc.rev140701.service.function.chains.state.ServiceFunctionChainState;
+import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfc.rev140701.service.function.chains.state.ServiceFunctionChainStateBuilder;
+import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfc.rev140701.service.function.chains.state.ServiceFunctionChainStateKey;
+import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp.rev140701.ServiceFunctionPaths;
+import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp.rev140701.service.function.paths.ServiceFunctionPath;
+import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp.rev140701.service.function.paths.ServiceFunctionPathBuilder;
+import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp.rev140701.service.function.paths.ServiceFunctionPathKey;
+import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp.rev140701.service.function.paths.service.function.path.ServicePathHop;
+import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp.rev140701.service.function.paths.service.function.path.ServicePathHopBuilder;
+import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sft.rev140701.service.function.types.ServiceFunctionType;
+import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sft.rev140701.service.function.types.service.function.type.SftServiceFunctionName;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -604,7 +585,7 @@ public class SfcProviderServicePathAPI extends SfcProviderAbstractAPI {
                         .build();
 
         ReadOnlyTransaction readTx = odlSfc.getDataProvider().newReadOnlyTransaction();
-        Optional<ServiceFunctionState> serviceFunctionStateObject = null;
+        Optional<ServiceFunctionState> serviceFunctionStateObject;
         try {
             serviceFunctionStateObject = readTx.read(LogicalDatastoreType.OPERATIONAL, sfStateIID).get();
         } catch (InterruptedException | ExecutionException e) {
@@ -612,7 +593,7 @@ public class SfcProviderServicePathAPI extends SfcProviderAbstractAPI {
             return;
         }
 
-        if ((serviceFunctionStateObject != null) &&
+        if ((serviceFunctionStateObject.isPresent()) &&
                 (serviceFunctionStateObject.get() instanceof ServiceFunctionState)) {
             serviceFunctionState = serviceFunctionStateObject.get();
             List<String> sfServiceFunctionPathList =
@@ -628,7 +609,10 @@ public class SfcProviderServicePathAPI extends SfcProviderAbstractAPI {
                 WriteTransaction writeTx = odlSfc.getDataProvider().newWriteOnlyTransaction();
                 writeTx.delete(LogicalDatastoreType.CONFIGURATION,
                         sfpIID);
-                writeTx.commit();
+                //writeTx.commit();
+                CheckedFuture<Void,TransactionCommitFailedException> submitFuture = writeTx.submit();
+                Futures.addCallback(submitFuture, new SfcDataStoreCallback());
+
                 // TODO: Need to consider failure of transaction
                 removedPaths.add(pathName);
             }
index 8108e77e5f40e84ff09af9956273fdc4ccf1fa55..a8f591c825d03f191ff430538d55a862313c730c 100755 (executable)
@@ -9,11 +9,15 @@
 package org.opendaylight.sfc.provider.api;
 
 import com.google.common.base.Optional;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sf.rev140701.service.functions.ServiceFunction;
 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sft.rev140701.ServiceFunctionTypes;
+import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sft.rev140701.ServiceFunctionTypesBuilder;
 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sft.rev140701.service.function.types.ServiceFunctionType;
 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sft.rev140701.service.function.types.ServiceFunctionTypeKey;
 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sft.rev140701.service.function.types.service.function.type.SftServiceFunctionName;
@@ -94,7 +98,8 @@ public class SfcProviderServiceTypeAPI extends SfcProviderAbstractAPI {
         printTraceStart(LOG);
         if (dataBroker != null) {
 
-            InstanceIdentifier<ServiceFunctionType> sftEntryIID = InstanceIdentifier.builder(ServiceFunctionTypes.class)
+            InstanceIdentifier<ServiceFunctionType> sftEntryIID =
+                    InstanceIdentifier.builder(ServiceFunctionTypes.class)
                     .child(ServiceFunctionType.class, sft.getKey()).toInstance();
 
             WriteTransaction writeTx = dataBroker.newWriteOnlyTransaction();
@@ -108,6 +113,12 @@ public class SfcProviderServiceTypeAPI extends SfcProviderAbstractAPI {
         return ret;
     }
 
+    /**
+     * This method is used to retrieve a Service Function Type from the DataStore
+     * <p>
+     * @param serviceFunctionTypeName Service Function Type Name
+     * @return Service Function Type Object
+     */
     protected ServiceFunctionType readServiceFunctionType(String serviceFunctionTypeName) {
         printTraceStart(LOG);
         ServiceFunctionType sft = null;
@@ -117,27 +128,32 @@ public class SfcProviderServiceTypeAPI extends SfcProviderAbstractAPI {
         sftIID = InstanceIdentifier.builder(ServiceFunctionTypes.class)
                 .child(ServiceFunctionType.class, serviceFunctionTypeKey).build();
 
-        if (dataBroker != null) {
-            ReadOnlyTransaction readTx = dataBroker.newReadOnlyTransaction();
-            Optional<ServiceFunctionType> serviceFunctionChainDataObject = null;
-            try {
-                serviceFunctionChainDataObject = readTx
-                        .read(LogicalDatastoreType.CONFIGURATION, sftIID).get();
-            } catch (InterruptedException | ExecutionException e) {
-                LOG.error("Could not read Service Function list for Type {} " +
-                        "", serviceFunctionTypeName);
-            }
-            if (serviceFunctionChainDataObject != null
-                    && serviceFunctionChainDataObject.isPresent()) {
-                sft = serviceFunctionChainDataObject.get();
-            }
+        ReadOnlyTransaction readTx = dataBroker.newReadOnlyTransaction();
+        Optional<ServiceFunctionType> serviceFunctionChainDataObject = null;
+        try {
+            serviceFunctionChainDataObject = readTx
+                    .read(LogicalDatastoreType.CONFIGURATION, sftIID).get();
+        } catch (InterruptedException | ExecutionException e) {
+            LOG.error("Could not read Service Function list for Type {} " +
+                    "", serviceFunctionTypeName);
+        }
+        if (serviceFunctionChainDataObject != null
+                && serviceFunctionChainDataObject.isPresent()) {
+            sft = serviceFunctionChainDataObject.get();
         }
+
         printTraceStop(LOG);
         return sft;
     }
 
+    /**
+     * This method is used to delete a Service Function Type from the DataStore
+     * <p>
+     * @param serviceFunctionTypeName Service Function Type Name
+     * @return Service Function Type Object
+     */
     protected boolean deleteServiceFunctionType(String serviceFunctionTypeName) {
-        boolean ret = false;
+        boolean ret;
         printTraceStart(LOG);
         ServiceFunctionTypeKey serviceFunctionTypeKey = new
                 ServiceFunctionTypeKey(serviceFunctionTypeName);
@@ -145,12 +161,20 @@ public class SfcProviderServiceTypeAPI extends SfcProviderAbstractAPI {
                 InstanceIdentifier.builder(ServiceFunctionTypes.class)
                 .child(ServiceFunctionType.class, serviceFunctionTypeKey).toInstance();
 
-        if (dataBroker != null) {
-            WriteTransaction writeTx = dataBroker.newWriteOnlyTransaction();
-            writeTx.delete(LogicalDatastoreType.CONFIGURATION, sftEntryIID);
-            writeTx.commit();
+        SfcDataStoreCallback sfcDataStoreCallback = new SfcDataStoreCallback();
+        WriteTransaction writeTx = dataBroker.newWriteOnlyTransaction();
+        writeTx.delete(LogicalDatastoreType.CONFIGURATION, sftEntryIID);
+
+        CheckedFuture<Void,TransactionCommitFailedException> submitFuture = writeTx.submit();
+        Futures.addCallback(submitFuture, sfcDataStoreCallback);
+        if (sfcDataStoreCallback.getTransactioSuccessful()) {
             ret = true;
+        } else {
+            ret = false;
+            LOG.error("Failed to create Service Function Type entry for name: {}",
+                    serviceFunctionTypeName);
         }
+
         printTraceStop(LOG);
         return ret;
     }
@@ -171,77 +195,71 @@ public class SfcProviderServiceTypeAPI extends SfcProviderAbstractAPI {
         return ret;
     }
 
+    /**
+     * This method reads and returns an object with all Service Function Types
+     * present in the Data Store
+     * <p>
+     * @param
+     * @return Nothing.
+     */
     protected ServiceFunctionTypes readAllServiceFunctionTypes() {
         ServiceFunctionTypes sfts = null;
         printTraceStart(LOG);
         InstanceIdentifier<ServiceFunctionTypes> sftsIID =
                 InstanceIdentifier.builder(ServiceFunctionTypes.class).toInstance();
 
-        if (odlSfc.getDataProvider() != null) {
-            ReadOnlyTransaction readTx = dataBroker.newReadOnlyTransaction();
-            Optional<ServiceFunctionTypes> serviceFunctionTypesDataObject;
-            try {
-                serviceFunctionTypesDataObject = readTx
-                        .read(LogicalDatastoreType.CONFIGURATION, sftsIID).get();
-                if (serviceFunctionTypesDataObject != null
-                        && serviceFunctionTypesDataObject.isPresent()) {
-                    sfts = serviceFunctionTypesDataObject.get();
-                }
-            } catch (InterruptedException | ExecutionException e) {
-                LOG.error("Could not read Service Function Types");
+
+        ReadOnlyTransaction readTx = dataBroker.newReadOnlyTransaction();
+        Optional<ServiceFunctionTypes> serviceFunctionTypesDataObject;
+        try {
+            serviceFunctionTypesDataObject = readTx
+                    .read(LogicalDatastoreType.CONFIGURATION, sftsIID).get();
+            if (serviceFunctionTypesDataObject != null
+                    && serviceFunctionTypesDataObject.isPresent()) {
+                sfts = serviceFunctionTypesDataObject.get();
             }
+        } catch (InterruptedException | ExecutionException e) {
+            LOG.error("Could not read All Service Function Types");
         }
+
         printTraceStop(LOG);
         return sfts;
     }
 
+    /**
+     * Delete all Service Function Types from data store
+     * <p>
+     * @param
+     * @return Nothing.
+     */
     protected boolean deleteAllServiceFunctionTypes() {
         boolean ret = false;
         printTraceStart(LOG);
-        if (odlSfc.getDataProvider() != null) {
-
-            InstanceIdentifier<ServiceFunctionTypes> sftsIID =
-                    InstanceIdentifier.builder(ServiceFunctionTypes.class).toInstance();
-            WriteTransaction writeTx = dataBroker.newWriteOnlyTransaction();
-            writeTx.delete(LogicalDatastoreType.CONFIGURATION, sftsIID);
-            writeTx.commit();
+        InstanceIdentifier<ServiceFunctionTypes> sftsIID =
+                InstanceIdentifier.builder(ServiceFunctionTypes.class).toInstance();
+        WriteTransaction writeTx = dataBroker.newWriteOnlyTransaction();
+        writeTx.delete(LogicalDatastoreType.CONFIGURATION,
+                sftsIID);
+        SfcDataStoreCallback sfcDataStoreCallback = new SfcDataStoreCallback();
+        CheckedFuture<Void,TransactionCommitFailedException> submitFuture = writeTx.submit();
+        Futures.addCallback(submitFuture, sfcDataStoreCallback);
+        if (sfcDataStoreCallback.getTransactioSuccessful()) {
             ret = true;
+        } else {
+            LOG.error("Could not delete all Service Function Types");
         }
         printTraceStop(LOG);
         return ret;
     }
 
-    /*
-    public static ServiceFunctionType getServiceFunctionTypeList(String typeName) {
-
-        LOG.debug("\n########## Start: {}", Thread.currentThread().getStackTrace()[1]);
-        InstanceIdentifier<ServiceFunctionType> sftListIID;
-        ServiceFunctionTypeKey serviceFunctionTypeKey;
-        serviceFunctionTypeKey = new ServiceFunctionTypeKey(typeName);
-
-        sftListIID = InstanceIdentifier.builder(ServiceFunctionTypes.class)
-                .child(ServiceFunctionType.class, serviceFunctionTypeKey).build();
-
-        ReadOnlyTransaction readTx = odlSfc.dataProvider.newReadOnlyTransaction();
-        Optional<ServiceFunctionType> serviceFunctionTypeObject = null;
-        try {
-            serviceFunctionTypeObject = readTx.read(LogicalDatastoreType.CONFIGURATION, sftListIID).get();
-        } catch (InterruptedException | ExecutionException e) {
-            e.printStackTrace();
-        }
-
-        if (serviceFunctionTypeObject != null &&
-                (serviceFunctionTypeObject.get() instanceof ServiceFunctionType)) {
-            ServiceFunctionType serviceFunctionType = serviceFunctionTypeObject.get();
-            printTraceStop(LOG);
-            return serviceFunctionType;
-        } else {
-            printTraceStop(LOG);
-            return null;
-        }
-    }
-    */
 
+    /**
+     * This method creates a Service function Type entry from a Service
+     * Function.
+     * <p>
+     * @param serviceFunction Service Function Object
+     * @return Nothing.
+     */
     public void createServiceFunctionTypeEntry(ServiceFunction serviceFunction) {
 
         printTraceStart(LOG);
@@ -249,6 +267,25 @@ public class SfcProviderServiceTypeAPI extends SfcProviderAbstractAPI {
         String sfkey = serviceFunction.getType();
         ServiceFunctionTypeKey serviceFunctionTypeKey = new ServiceFunctionTypeKey(sfkey);
 
+        if (readAllServiceFunctionTypes() == null) {
+            InstanceIdentifier<ServiceFunctionTypes> sftIID;
+            sftIID = InstanceIdentifier.builder(ServiceFunctionTypes.class).build();
+            ServiceFunctionTypesBuilder serviceFunctionTypesBuilder = new ServiceFunctionTypesBuilder();
+
+            WriteTransaction writeTx = dataBroker.newWriteOnlyTransaction();
+            writeTx.put(LogicalDatastoreType.CONFIGURATION,
+                    sftIID, serviceFunctionTypesBuilder.build());
+            //writeTx.commit();
+            SfcDataStoreCallback sfcDataStoreCallback = new SfcDataStoreCallback();
+            CheckedFuture<Void,TransactionCommitFailedException> submitFuture = writeTx.submit();
+            Futures.addCallback(submitFuture, sfcDataStoreCallback);
+            if (!sfcDataStoreCallback.getTransactioSuccessful()) {
+                LOG.error("Failed to create top level Service Function Type object");
+
+            }
+
+        }
+
         //Build the instance identifier all the way down to the bottom child
 
         SftServiceFunctionNameKey sftServiceFunctionNameKey =
@@ -271,12 +308,27 @@ public class SfcProviderServiceTypeAPI extends SfcProviderAbstractAPI {
         WriteTransaction writeTx = dataBroker.newWriteOnlyTransaction();
         writeTx.merge(LogicalDatastoreType.CONFIGURATION,
                 sftentryIID, sftServiceFunctionName, true);
-        writeTx.commit();
+        //writeTx.commit();
+        SfcDataStoreCallback sfcDataStoreCallback = new SfcDataStoreCallback();
+        CheckedFuture<Void,TransactionCommitFailedException> submitFuture = writeTx.submit();
+        Futures.addCallback(submitFuture, sfcDataStoreCallback);
+        if (!sfcDataStoreCallback.getTransactioSuccessful()) {
+            LOG.error("Failed to create Service Function Type entry for SF: {}",
+                    serviceFunction.getName());
+
+        }
 
         printTraceStop(LOG);
 
     }
 
+    /**
+     * This method is used to delete a Service Function Type from the Data Store
+     * that was instantiated from a Service Function passed as a parameter
+     * <p>
+     * @param serviceFunction Service Function object
+     * @return Service Function Type Object
+     */
     public void deleteServiceFunctionTypeEntry(ServiceFunction serviceFunction) {
 
         LOG.debug("\n########## Start: {}", Thread.currentThread().getStackTrace()[1]);
@@ -294,7 +346,9 @@ public class SfcProviderServiceTypeAPI extends SfcProviderAbstractAPI {
         WriteTransaction writeTx = dataBroker.newWriteOnlyTransaction();
         writeTx.delete(LogicalDatastoreType.CONFIGURATION,
                 sftentryIID);
-        writeTx.commit();
+        SfcDataStoreCallback sfcDataStoreCallback = new SfcDataStoreCallback();
+        CheckedFuture<Void,TransactionCommitFailedException> submitFuture = writeTx.submit();
+        Futures.addCallback(submitFuture, sfcDataStoreCallback);
 
         printTraceStop(LOG);
     }
diff --git a/sfc-py/.gitignore b/sfc-py/.gitignore
new file mode 100644 (file)
index 0000000..caddf55
--- /dev/null
@@ -0,0 +1,34 @@
+*.class
+*.pyc
+**/target
+bin/
+dist
+**/logs
+products
+repository
+workspace
+*~
+1target
+target-ide
+.classpath
+.project
+.settings
+MANIFEST.MF
+opendaylight/northbound/integrationtest/logs/*
+*.ipr
+*.iml
+*.iws
+.idea
+xtend-gen
+yang-gen-config
+yang-gen-sal
+classes
+out/
+.externalToolBuilders
+maven-eclipse.xml
+.DS_STORE
+.metadata
+.hg
+/target
+node_modules/
+coverage/
diff --git a/sfc-py/odl2ovs.py b/sfc-py/odl2ovs.py
new file mode 100644 (file)
index 0000000..7eb9fde
--- /dev/null
@@ -0,0 +1,674 @@
+__author__ = "Paul Quinn, Reinaldo Penno"
+__copyright__ = "Copyright(c) 2014, Cisco Systems, Inc."
+__version__ = "0.2"
+__status__ = "alpha"
+
+#
+# Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v1.0 which accompanies this distribution,
+# and is available at http://www.eclipse.org/legal/epl-v10.html
+
+""" SFF REST Server. This Server should be co-located with a OVS switch """
+
+
+import logging
+import socket
+
+from flask import *
+from random import randint
+import sys
+import getopt
+import json
+import requests
+from odl2ovs_cli import *
+
+app = Flask(__name__)
+my_topo = {}
+
+sff_topo = {}
+path = {}
+
+# ODL IP:port
+ODLIP = "127.0.0.1:8181"
+# Static URLs for testing
+SF_URL = "http://" + ODLIP + "/restconf/config/service-function:service-functions/"
+SFC_URL = "http://" + ODLIP + "/restconf/config/service-function-chain:service-function-chains/"
+SFF_URL = "http://" + ODLIP + "/restconf/config/service-function-forwarder:service-function-forwarders/"
+SFT_URL = "http://" + ODLIP + "/restconf/config/service-function-type:service-function-types/"
+SFP_URL = "http://" + ODLIP + "/restconf/config/service-function-path:service-function-paths/"
+
+SFF_PARAMETER_URL = "http://{}/restconf/config/service-function-forwarder:service-function-forwarders/"
+
+USERNAME = "admin"
+PASSWORD = "admin"
+
+logger = logging.getLogger(__name__)
+
+
+def sffinit():
+    """
+    This function is used when testing without actual OVS switch
+    :return:
+    """
+
+    sff_topo_init = {
+        "service-function-forwarders": {
+            "service-function-forwarder": [
+                {
+                    "name": "SFF1",
+                    "service-node": "OVSDB1",
+                    "sff-data-plane-locator": [
+                        {
+                            "name": "eth0",
+                            "service-function-forwarder-ovs:ovs-bridge": {
+                                "bridge-name": "br-tun",
+                                "uuid": "4c3778e4-840d-47f4-b45e-0988e514d26c"
+                            },
+                            "data-plane-locator": {
+                                "port": 4789,
+                                "ip": "10.100.100.1",
+                                "transport": "service-locator:vxlan-gpe"
+                            }
+                        }
+                    ],
+                    "rest-uri": "http://198.18.134.23",
+                    "service-function-dictionary": [
+                        {
+                            "name": "SF1",
+                            "type": "dp1",
+                            "sff-sf-data-plane-locator": {
+                                "port": 4789,
+                                "ip": "10.1.1.4",
+                                "transport": "service-locator:vxlan-gpe",
+                                "service-function-forwarder-ovs:ovs-bridge": {
+                                    "bridge-name": "br-int"
+                                }
+                            }
+                        },
+                        {
+                            "name": "SF2",
+                            "type": "napt44",
+                            "sff-sf-data-plane-locator": {
+                                "port": 4789,
+                                "ip": "10.1.1.5",
+                                "transport": "service-locator:vxlan-gpe",
+                                "service-function-forwarder-ovs:ovs-bridge": {
+                                    "bridge-name": "br-int"
+                                }
+                            }
+                        }
+                    ],
+                    "classifier": "acl-sfp-1",
+                    "ip-mgmt-address": "198.18.134.23"
+                },
+                {
+                    "name": "SFF2",
+                    "service-node": "OVSDB2",
+                    "sff-data-plane-locator": [
+                        {
+                            "name": "eth0",
+                            "service-function-forwarder-ovs:ovs-bridge": {
+                                "bridge-name": "br-tun",
+                                "uuid": "fd4d849f-5140-48cd-bc60-6ad1f5fc0a0"
+                            },
+                            "data-plane-locator": {
+                                "port": 4789,
+                                "ip": "10.100.100.2",
+                                "transport": "service-locator:vxlan-gpe"
+                            }
+                        }
+                    ],
+                    "rest-uri": "http://198.18.134.23",
+                    "service-function-dictionary": [
+                        {
+                            "name": "SF3",
+                            "type": "firewall",
+                            "sff-sf-data-plane-locator": {
+                                "port": 4789,
+                                "ip": "10.1.2.6",
+                                "transport": "service-locator:vxlan-gpe",
+                                "service-function-forwarder-ovs:ovs-bridge": {
+                                    "bridge-name": "br-int"
+                                }
+                            }
+                        }
+                    ],
+                    "ip-mgmt-address": "198.18.134.24"
+                }
+            ]
+        }
+    }
+
+    return sff_topo_init
+
+
+def pathinit():
+    """
+    This function is used when testing without actual OVS switch
+    :return:
+    """
+    path_init = {
+        "service-function-paths": {
+            "service-function-path": [
+                {
+                    "name": "Path-1-SFC1",
+                    "path-id": 1,
+                    "starting-index": 3,
+                    "service-chain-name": "SFC1",
+                    "service-path-hop": [
+                        {
+                            "hop-number": 0,
+                            "service-function-name": "SF1",
+                            "service_index": 3,
+                            "service-function-forwarder": "SFF1"
+                        },
+                        {
+                            "hop-number": 1,
+                            "service-function-name": "SF2",
+                            "service_index": 2,
+                            "service-function-forwarder": "SFF1"
+                        },
+                        {
+                            "hop-number": 2,
+                            "service-function-name": "SF3",
+                            "service_index": 1,
+                            "service-function-forwarder": "SFF2"
+                        }
+                    ]
+                }
+            ]
+        }
+    }
+
+    return path_init
+
+
+# the following dictionaries are for testing only.  Remove when running on OVS.
+def get_bridge_info():
+    b1 = {
+        'status': '{}',
+        'fail_mode': '[]',
+        'datapath_id': '"0000e21a84dd0c4c"',
+        'datapath_type': '""',
+        'sflow': '[]',
+        'mirrors': '[]',
+        'ipfix': '[]',
+        '_uuid': 'dd841ae1-0a6e-4c0c-b24c-059e7b0b87f8',
+        'other_config': '{}',
+        'flood_vlans': '[]',
+        'stp_enable': 'false',
+        'controller': '[]',
+        'mcast_snooping_enable': 'false',
+        'flow_tables': '{}',
+        'ports': '[60ce3635-70d2-4c48-98f6-cefd65ab0e58]',
+        'external_ids': '{bridge-id="SFF1"}',
+        'netflow': '[]',
+        'protocols': '[]',
+        'name': '"br-int"'
+    }
+
+    b2 = {
+        'status': '{}',
+        'fail_mode': '[]',
+        'datapath_id': '"000052f810c06148"',
+        'datapath_type': '""',
+        'sflow': '[]',
+        'mirrors': '[]',
+        'ipfix': '[]',
+        '_uuid': 'c010f853-5c8a-4861-9e53-050981fbc121',
+        'other_config': '{}',
+        'flood_vlans': '[]',
+        'stp_enable': 'false',
+        'controller': '[]',
+        'mcast_snooping_enable': 'false',
+        'flow_tables': '{}',
+        'ports': '[4a194fdd-ed59-47cf-998b-7c996c46e3e6]',
+        'external_ids': '{}',
+        'netflow': '[]',
+        'protocols': '[]',
+        'name': '"br-tun"'
+    }
+
+    # br_list = []
+    br_dict_list = []
+    # bc = 0
+    # br_dict={}
+
+    # bridges = subprocess.check_output(['ovs-vsctl', 'list-br'])
+
+    # for line in bridges.split('\n'):
+    # br_list.append(line)
+
+    # while bc < (len(br_list) - 1):
+    # b = subprocess.check_output(['ovs-vsctl', 'list', 'bridge', br_list[b]])
+    # for row in b.split('\n'):
+    # if ': ' in row:
+    # key, value = row.split(': ')
+    # br_dict[key.strip()] = value.strip()
+    #  br_dict_list.append(br_dict)
+    #  b = b+1
+
+    # test code
+
+    br_dict_list.append(b1)
+    br_dict_list.append(b2)
+    return br_dict_list
+
+
+# This function does not work if machine has more than one IP/interface
+def get_my_ip():
+    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+    s.connect(('8.8.8.8', 80))
+    myip = (s.getsockname()[0])
+    s.close()
+    myip = "http://" + myip + ":/paths"
+    return myip
+
+
+def parse_bridges(bridge_list):
+    #num_of_bridges = len(bridge_list)
+    all_bridges = []
+    br_dict = {}
+
+    for bridge in bridge_list:
+        if bridge['name'] == '"br-tun"' or '"br-int"':
+            br_dict = {
+                'name': bridge['name'][1:-1],
+                'external_ids': bridge['external_ids'], 'uuid': bridge['_uuid']
+            }
+        all_bridges.append(br_dict)
+    return all_bridges
+
+
+# Not used anymore
+def who_am_i(path, bridges):
+    for path in path['service-function-paths']['service-function-path']:
+        for sff in path['service-path-hop']:
+            for bridge in bridges:
+                if sff['service-function-forwarder'] == bridge['external_ids'][12:-2]:
+                    return sff['service-function-forwarder']
+
+
+def who_am_i_sfp(service_path):
+    """
+    Determines the name of the local attached SFF
+    :param service_path: A single Service Function Path
+    :return: The name of the local attached SFF
+    """
+    ovsbridges = get_bridge_info()
+    bridges = parse_bridges(ovsbridges)
+    for sff in service_path['service-path-hop']:
+        for bridge in bridges:
+            if sff['service-function-forwarder'] == bridge['external_ids'][12:-2]:
+                return sff['service-function-forwarder']
+    return None
+
+
+def who_am_i_sff():
+    """
+    Determines the name of the local attached SFF by checking
+    against the collections of all known SFFs
+    :return: The name of the local attached SFF
+    """
+    ovsbridges = get_bridge_info()
+    bridges = parse_bridges(ovsbridges)
+    for bridge in bridges:
+        if bridge['external_ids'][12:-2] in sff_topo.keys():
+            return bridge['external_ids'][12:-2]
+    return None
+
+
+# Not used anymore
+def build_a_path(path, my_sff):
+    # me = 'SFF-bootstrap'
+    sflist = []
+    nextsff = {}
+    sfdict = {}
+    count = 0
+    pid = 0
+
+    for path in path['service-function-paths']['service-function-path']:
+        pid = path['path-id']
+        for sf in path['service-path-hop']:
+            if sf['service-function-forwarder'] == my_sff:
+                sfdict['sff'] = sf['service-function-forwarder']
+                sfdict['pid'] = path['path-id']
+                sfdict['name'] = sf['service-function-name']
+                sfdict['index'] = sf['service_index']
+                find_sf_loc(sfdict)
+                sflist.append(sfdict)
+                sfdict = {}
+                count += 1
+    nextsff['sff-name'] = path['service-path-hop'][count]['service-function-forwarder']
+    nextsff['sff-index'] = path['service-path-hop'][count]['service_index']
+    nextsffloc = find_sff_loc(nextsff)
+    return sflist, nextsffloc, nextsff, my_sff, pid
+
+
+def build_service_path(service_path, my_sff_name):
+    """
+    Builds a dictionary of the local attached Service Functions
+    :param path: A single Service Function Path
+    :param my_sff_name: The name of the local attached SFF
+    :return:
+    """
+    # my_sff = 'SFF-bootstrap'
+    sflist = []
+    nextsff = {}
+    sfdict = {}
+    count = 0
+
+    for service_hop in service_path['service-path-hop']:
+        if service_hop['service-function-forwarder'] == my_sff_name:
+            sfdict['sff'] = service_hop['service-function-forwarder']
+            sfdict['pid'] = service_path['path-id']
+            sfdict['name'] = service_hop['service-function-name']
+            sfdict['index'] = service_hop['service_index']
+            sfdict['locator'] = find_sf_locator(sfdict['name'], sfdict['sff'])
+            if sfdict['locator'] is None:
+                logger.error("Could not find data plane locator for SF: %s", sfdict['name'])
+            sflist.append(sfdict)
+            sfdict = {}
+            count += 1
+    nextsff['sff-name'] = service_path['service-path-hop'][count]['service-function-forwarder']
+    nextsff['sff-index'] = service_path['service-path-hop'][count]['service_index']
+    nextsffloc = find_sff_locator(nextsff['sff-name'])
+    if nextsffloc is None:
+        logger.error("Could not find data plane locator for SFF: %s", nextsff['sff-name'])
+    return sflist, nextsffloc, nextsff
+
+
+def find_sf_locator(sf_name, sff_name):
+    """
+    Looks for the SF name  within the service function
+    dictionary of sff_name. If found, return the
+    corresponding data plane locator
+
+    :param sfdict: A dictionary with a single SF attributes
+    :return: SF data plane locator
+    """
+    service_dictionary = sff_topo[sff_name]['service-function-dictionary']
+    for service_function in service_dictionary:
+        if sf_name == service_function['name']:
+            return service_function['sff-sf-data-plane-locator']['ip']
+    return None
+
+
+def find_sff_locator(sff_name):
+    """
+    For a given SFF name, look into local SFF topology for a match
+    and returns the corresponding data plane locator
+    :param sff_name:
+    :return: SFF data plane locator
+    """
+    try:
+        return sff_topo[sff_name]['sff-data-plane-locator'][0]['data-plane-locator']['ip']
+    except KeyError, e:
+        msg = "SFF {} locator not found".format(sff_name)
+        logger.warning(msg)
+        return None
+    except:
+        logger.warning("Unexpected exception, re-raising it")
+        raise
+
+
+# Not used anymore
+def find_sff_loc(sff):
+    for sffi in sff_topo['service-function-forwarders']['service-function-forwarder']:
+        if sffi['name'] == sff['sff-name']:
+            return sffi['sff-data-plane-locator'][0]['data-plane-locator']['ip']
+
+
+# Not used anymore
+def find_sf_loc(sfdict):
+    count = 0
+    while count < len(sff_topo['service-function-forwarders']['service-function-forwarder']):
+        if sff_topo['service-function-forwarders']['service-function-forwarder'] \
+                [count]['name'] == sfdict['sff']:
+            for sfi in (sff_topo['service-function-forwarders']
+                        ['service-function-forwarder']
+                        [count]['service-function-dictionary']):
+                if sfdict['name'] == sfi['name']:
+                    sfdict['locator'] = sfi['sff-sf-data-plane-locator']['ip']
+        count += 1
+        return
+
+
+def mytopo(nextsffloc, vxlanid):
+    global my_topo
+    if nextsffloc in my_topo.keys():
+        return my_topo[nextsffloc]
+    else:
+        vxlan = 'vxlan' + str(vxlanid)
+        my_topo[nextsffloc] = vxlan
+        vxlanid += 1
+        return vxlanid
+
+
+def cli():
+    global path
+    global sff_topo
+    path = pathinit()
+    sff_topo = sffinit()
+    ovsbridges = get_bridge_info()
+    bridge_info = parse_bridges(ovsbridges)
+    my_sff = who_am_i(path, bridge_info)
+    vxlanid = 0
+    key = hex(randint(1, 16777216))
+    build_a_path(path, my_sff)
+    mysflist, nextsffloc, nextsff, my_sff, pid = build_a_path(path, my_sff)
+    vxlanid = mytopo(nextsffloc, vxlanid)
+    vxlanid = cli_local(mysflist, vxlanid, key)
+    cli_nextsff(nextsffloc, nextsff, key, vxlanid, pid)
+    return
+
+
+# Not used anymore
+def ovsbuildit(path):
+    print "BUILDING CHAIN..."
+    ovsbridges = get_bridge_info()
+    bridge_info = parse_bridges(ovsbridges)
+    my_sff = who_am_i(path, bridge_info)
+    # my_topo = {}
+    vxlanid = 0
+    key = hex(randint(1, 16777216))
+
+    mysflist, nextsffloc, nextsff, me, pid = build_a_path(path, my_sff)
+    my_topo, vxlanid = mytopo(nextsffloc, vxlanid)
+    vxlanid = ovs_cli_local(mysflist, vxlanid, key)
+    ovs_cli_nextsff(nextsffloc, nextsff, key, vxlanid, pid)
+    return
+
+
+def ovsbuild_one_path(service_path):
+    """
+    :param path: A single Service Function Path
+    :return: Nothing
+    """
+    logger.info("BUILDING CHAIN...")
+    my_sff_name = who_am_i_sfp(service_path)
+    if my_sff_name is None:
+        logger.info("Service path does not contain local SFF")
+        return
+    # Is this correct?
+    vxlanid = 0
+    key = hex(randint(1, 16777216))
+
+    mysflist, nextsffloc, nextsff = build_service_path(service_path, my_sff_name)
+    vxlanid = mytopo(nextsffloc, vxlanid)
+    vxlanid = ovs_cli_local(mysflist, vxlanid, key)
+    pid = mysflist[0]['pid']
+    ovs_cli_nextsff(nextsffloc, nextsff, key, vxlanid, pid)
+    return
+
+
[email protected]('/config/service-function-path:service-function-paths/', methods=['GET'])
+def get_paths():
+    return jsonify({'Service paths': path})
+
+
[email protected]('/config/service-function-forwarder:service-function-forwarders/', methods=['GET'])
+def get_sffs():
+    return jsonify({'SFFs': sff_topo})
+
+
[email protected]('/config/service-function-path:service-function-paths/', methods=['PUT'])
+def create_paths():
+    global path
+    if not request.json:
+        abort(400)
+    else:
+        path = {
+            'service-function-paths': request.json['service-function-paths']
+        }
+    if any(sff_topo):
+        ovsbuildit(path)
+    return jsonify({'path': path}), 201
+
+
[email protected]('/config/service-function-path:service-function-paths/service-function-path/<sfpname>', methods=['PUT'])
+def create_path(sfpname):
+    global path
+    if not request.json:
+        abort(400)
+    else:
+        # print json.dumps(sfpjson)
+        # sfpj_name = sfpjson["service-function-path"][0]['name']
+        path[sfpname] = request.get_json()["service-function-path"][0]
+
+    if any(sff_topo):
+        ovsbuild_one_path(path[sfpname])
+    return jsonify({'path': path}), 201
+
+
[email protected]('/config/service-function-path:service-function-paths/service-function-path/<sfpname>', methods=['DELETE'])
+def delete_path(sfpname):
+    global path
+    try:
+        del path[sfpname]
+    except KeyError, e:
+        msg = "SFP name {} not found, message".format(sfpname)
+        logger.warning(msg)
+        return msg, 404
+    except:
+        logger.warning("Unexpected exception, re-raising it")
+        raise
+    return '', 204
+
+
[email protected]('/config/service-function-forwarder:service-function-forwarders/service-function-forwarder/<sffname>',
+           methods=['PUT'])
+def create_sff(sffname):
+    global sff_topo
+    if not request.json:
+        abort(400)
+    else:
+        sff_topo[sffname] = request.get_json()['service-function-forwarder'][0]
+    if any(path):
+        ovsbuild_one_path(path)
+    return jsonify({'sff': sff_topo}), 201
+
+
[email protected]('/config/service-function-forwarder:service-function-forwarders/service-function-forwarder/<sffname>',
+           methods=['DELETE'])
+def delete_sff(sffname):
+    global sff_topo
+    try:
+        del sff_topo[sffname]
+    except KeyError, e:
+        msg = "SFF name {} not found, message".format(sffname)
+        logger.warning(msg)
+        return msg, 404
+    except:
+        logger.warning("Unexpected exception, re-raising it")
+        raise
+    return '', 204
+
+
[email protected]('/config/service-function-forwarder:service-function-forwarders/', methods=['PUT'])
+def create_sffs():
+    global sff_topo
+    if not request.json:
+        abort(400)
+    else:
+        me = ""
+        sff_topo = {
+            'service-function-forwarders': request.json['service-function-forwarders']
+        }
+    if any(path):
+        ovsbuildit(path)
+    return jsonify({'sff': sff_topo}), 201
+
+
[email protected]('/config/service-function-forwarder:service-function-forwarders/', methods=['DELETE'])
+def delete_sffs():
+    global sff_topo
+    sff_topo = {}
+    return jsonify({'sff': sff_topo}), 201
+
+
+def page_not_found(e):
+    return render_template('404.html'), 404
+
+
+def get_sff_from_odl(odl_ip_port):
+    """
+    Retrieves the list fo configured SFFs from ODL
+    :return: Nothing
+    """
+    s = requests.Session()
+    print ("Getting SFF information from ODL... \n")
+    r = s.get(SFF_PARAMETER_URL.format(odl_ip_port), stream=False, auth=(USERNAME, PASSWORD))
+    if r.status_code == 200:
+        sff_json = json.loads(r.text)['service-function-forwarders']['service-function-forwarder']
+        for sff in sff_json:
+            sff_topo[sff['name']] = sff
+    else:
+        print ("=>Failed to GET SFF from ODL \n")
+
+
+def main(argv):
+    global ODLIP
+    try:
+        logging.basicConfig(level=logging.INFO)
+        opt, args = getopt.getopt(argv, "hrc", ["help", "rest", "cli", "odl-get-sff", "odl-ip-port="])
+    except getopt.GetoptError:
+        print 'rest2ovs --help | --rest | --cli | --odl-get-sff | --odl-ip-port'
+        sys.exit(2)
+
+    odl_get_sff = False
+    rest = False
+    for opt, arg in opt:
+        if opt == "--odl-get-sff":
+            odl_get_sff = True
+            continue
+
+        if opt == "--odl-ip-port":
+            ODLIP = arg
+            continue
+
+        if opt in ('-h', '--help'):
+            print 'rest2ovs -m cli | rest --odl-get-sff --odl-ip-port'
+            sys.exit()
+
+        if opt in ('-c', '--cli'):
+            cli()
+            sys.exit()
+
+        if opt in ('-r', '--rest'):
+            rest = True
+
+    if odl_get_sff:
+        get_sff_from_odl(ODLIP)
+
+    if rest:
+        app.debug = True
+        app.run(host='0.0.0.0')
+
+
+if __name__ == "__main__":
+    main(sys.argv[1:])
diff --git a/sfc-py/odl2ovs_cli.py b/sfc-py/odl2ovs_cli.py
new file mode 100644 (file)
index 0000000..5d53933
--- /dev/null
@@ -0,0 +1,68 @@
+__author__ = "Paul Quinn, Reinaldo Penno"
+__copyright__ = "Copyright(c) 2014, Cisco Systems, Inc."
+__version__ = "0.2"
+__status__ = "alpha"
+
+
+#
+# Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v1.0 which accompanies this distribution,
+# and is available at http://www.eclipse.org/legal/epl-v10.html
+
+import subprocess
+
+def cli_local(sfdpinfo, vxlan, key):
+    sfi = 0
+    if len(sfdpinfo) > 1:
+        for sf in sfdpinfo:
+            part1 = 'ovs-vsctl add-port br-int ' + 'vxlan' + str(vxlan) + ' -- set interface vxlan' + str(vxlan) + ' options:dst_port=6633 type=vxlan options:remote_ip=' + sfdpinfo[sfi]['locator']
+            part2 = ' options:key=' + str(key) + ' options:nsp=' + str(sfdpinfo[sfi]['pid']) + ' options:nsi=' + str(sfdpinfo[sfi]['index'])
+            cli = part1 + part2
+            print cli
+            #subprocess.call([cli], shell=True)
+            vxlan += 1
+            sfi += 1
+    else:
+        print "No locally attached services on SFF"
+    return vxlan
+
+
+def ovs_cli_local(sfdpinfo, vxlan, key):
+    sfi = 0
+    if len(sfdpinfo) > 1:
+        for sf in sfdpinfo:
+            part1 = 'ovs-vsctl add-port br-int ' + 'vxlan' + str(vxlan) + '-- set interface vxlan' + str(vxlan) + ' options:dst_port=6633 type=vxlan options:remote_ip=' + sfdpinfo[sfi]['locator']
+            part2 = ' options:key=' + str(key) + ' options:nsp=' + str(sfdpinfo[sfi]['pid']) + ' options:nsi=' + str(sfdpinfo[sfi]['index'])
+            cli = part1 + part2
+            subprocess.call([cli], shell=True)
+            vxlan += 1
+            sfi += 1
+            print cli
+    else:
+        print "No locally attached services on SFF"
+    return vxlan
+
+
+def cli_nextsff(nextsffloc, nextsff, key, vxlan, pid):
+    part1 = 'ovs-vsctl add-port br-tun ' + 'vxlan' + str(vxlan) + ' -- set interface vxlan' + str(vxlan) + ' options:dst_port=6633 type=vxlan options:remote_ip=' + nextsffloc
+    part2 = ' options:key=' + str(key) + ' options:nsp=' + str(pid) + ' options:nsi=' + str(nextsff['sff-index'])
+    cli = part1 + part2
+    print cli
+    #subprocess.call([cli], shell=True)
+    vxlan += 1
+    return
+
+
+def ovs_cli_nextsff(nextsffloc, nextsff, key, vxlan, path):
+    part1 = 'ovs-vsctl add-port br-tun ' + 'vxlan' + str(vxlan) + ' -- set interface vxlan' + str(vxlan) + ' options:dst_port=6633 type=vxlan options:remote_ip=' + nextsffloc
+    part2 = ' options:key=' + str(key) + ' options:nsp=' + str(path) + ' options:nsi=' + str(nextsff['sff-index'])
+    cli = part1 + part2
+
+    print cli
+    subprocess.call([cli], shell=True)
+
+    vxlan += 1
+    return
diff --git a/sfc-py/pysf_oldnsh.py b/sfc-py/pysf_oldnsh.py
new file mode 100644 (file)
index 0000000..29fa51d
--- /dev/null
@@ -0,0 +1,219 @@
+__author__ = "Jim Guichard"
+__copyright__ = "Copyright(c) 2014, Cisco Systems, Inc."
+__version__ = "0.1"
+__email__ = "[email protected]"
+__status__ = "alpha"
+
+#
+# Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v1.0 which accompanies this distribution,
+# and is available at http://www.eclipse.org/legal/epl-v10.html
+
+"""Network Service Header (NSH) Enabled Service Function"""
+
+import argparse
+import asyncio
+import sys, struct, socket
+import binascii
+from ctypes import *
+
+try:
+    import signal
+except ImportError:
+    signal = None
+
+class BASEHEADER(Structure):
+    _fields_ = [("version", c_ushort, 2),
+                ("flags", c_ushort, 8),
+                ("length", c_ushort, 6),
+                ("next_protocol", c_uint, 16),
+                ("service_path", c_uint, 24),
+                ("service_index", c_uint, 8)]
+
+class CONTEXTHEADER(Structure):
+    _fields_ = [("network_platform", c_uint),
+                ("network_shared", c_uint),
+                ("service_platform",c_uint),
+                ("service_shared", c_uint)]
+
+# Decode base NSH header and context headers
+
+base_values = BASEHEADER()
+ctx_values = CONTEXTHEADER()
+
+class MyFwService:
+    
+    def connection_made(self, transport):
+        self.transport = transport
+    
+    def datagram_received(self, data, addr):
+        print('\nfw service received packet from SFF:\n', addr, binascii.hexlify(data))
+        rw_data = process_incoming_packet(data)
+        self.transport.sendto(rw_data, addr)
+        loop.stop()
+    
+    def connection_refused(self, exc):
+        print('Connection refused:', exc)
+    
+    def connection_lost(self, exc):
+        print('closing transport', exc)
+        loop = asyncio.get_event_loop()
+        loop.stop()
+
+class MyNatService:
+    
+    def connection_made(self, transport):
+        self.transport = transport
+    
+    def datagram_received(self, data, addr):
+        print('\nnat service received packet from SFF:\n', addr, binascii.hexlify(data))
+        print('\n')
+        rw_data = process_incoming_packet(data)
+        self.transport.sendto(rw_data, addr)
+        loop.stop()
+    
+    def connection_refused(self, exc):
+        print('Connection refused:', exc)
+    
+    def connection_lost(self, exc):
+        print('closing transport', exc)
+        loop = asyncio.get_event_loop()
+        loop.stop()
+
+class MyDpiService:
+    
+    def connection_made(self, transport):
+        self.transport = transport
+    
+    def datagram_received(self, data, addr):
+        print('\ndpi service received packet from SFF:\n', addr, binascii.hexlify(data))
+        print('\n')
+        rw_data = process_incoming_packet(data)
+        self.transport.sendto(rw_data, addr)
+        loop.stop()
+    
+    def connection_refused(self, exc):
+        print('Connection refused:', exc)
+    
+    def connection_lost(self, exc):
+        print('closing transport', exc)
+        loop = asyncio.get_event_loop()
+        loop.stop()
+
+def process_incoming_packet(data):
+    print('Processing recieved packet')
+    rw_data = bytearray(data)
+    decode_baseheader(data)
+    decode_contextheader(data)
+    base_values.service_index -= 1
+    set_service_index(rw_data, base_values.service_index)
+    return(rw_data)
+
+def decode_baseheader(payload):
+    # Base Service header
+    #base_header = payload[8:17] #starts at offset 8 of payload
+    base_header = payload[7:16]
+    
+    start_idx, base_values.md_type, base_values.next_protocol, path_idx = struct.unpack('!H B H I', base_header)
+    
+    base_values.version = start_idx >> 14
+    base_values.flags = start_idx >> 6
+    base_values.length = start_idx >> 0
+    base_values.service_path = path_idx >> 8;
+    base_values.service_index = path_idx & 0x000000FF
+    
+    if (__debug__ == False):
+        print ("\nBase NSH Header Decode:")
+        print (binascii.hexlify(base_header))
+        #print ('NSH Version:', base_values.version)
+        #print ('NSH base header flags:', base_values.flags)
+        #print ('NSH base header length:', base_values.length)
+        #print ('NSH MD-type:', base_values.md_type)
+        #print ('NSH base header next protocol:', base_values.next_protocol)
+        print ('Service Path Identifier:', base_values.service_path)
+        print ('Service Index:', base_values.service_index)
+
+# Decode the NSH context headers for a received packet at this SFF.
+
+def decode_contextheader(payload):
+    # Context header
+    context_header = payload[16:32]
+    
+    ctx_values.network_platform, ctx_values.network_shared, ctx_values.service_platform, \
+        ctx_values.service_shared = struct.unpack('!I I I I', context_header)
+    
+    if (__debug__ == False):
+        print ("\nNSH Context Header Decode:")
+        print (binascii.hexlify(context_header))
+        print ('Network Platform Context:', ctx_values.network_platform)
+        print ('Network Shared Context:', ctx_values.network_shared)
+        print ('Service Platform Context:', ctx_values.service_platform)
+        print ('Service Shared Context:', ctx_values.service_shared)
+
+def set_service_index(rw_data, service_index):
+    rw_data[16] = service_index
+
+def start_server(loop, addr, service, myip):
+    t = asyncio.Task(loop.create_datagram_endpoint(
+        service, local_addr=(myip, 6633)))
+    loop.run_until_complete(t)
+    print('Connection made with SFF:', addr)
+    print('Listening for packets on port:', myip)
+
+def find_service(service):
+    if service == 'fw':
+        return(MyFwService)
+    elif service == 'nat':
+        return(MyNatService)
+    elif service == 'dpi':
+        return(MyDpiService)
+
+def get_service_ip():
+    # Let's find a local IP address to use as the source IP of client generated packets
+    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+    try:
+        s.connect(('8.8.8.8', 80))
+        client = (s.getsockname()[0])
+    except socket.error:
+        client = "Unknown IP"
+    finally:
+        s.close()
+    return client
+
+ARGS = argparse.ArgumentParser(description="NSH Service Function")
+ARGS.add_argument(
+                  '--type', action="store", dest='type',
+                  default=False, help='Run service function. Options: fw, nat, dpi')
+ARGS.add_argument(
+                  '--host', action="store", dest='host',
+                  default='127.0.0.1', help='SFF host name')
+ARGS.add_argument(
+                  '--port', action="store", dest='port',
+                  default=4789, type=int, help='SFF port number')
+
+if __name__ == '__main__':
+    args = ARGS.parse_args()
+    if ':' in args.host:
+        args.host, port = args.host.split(':', 1)
+        args.port = int(port)
+    
+    if (not (args.type)):
+        print('Please specify --type\n')
+        ARGS.print_help()
+    else:
+        loop = asyncio.get_event_loop()
+        if signal is not None:
+            loop.add_signal_handler(signal.SIGINT, loop.stop)
+        
+        if '--type' in sys.argv:
+            #local_ip = get_service_ip()
+            local_ip = "10.1.1.4"
+            service = find_service(args.type)
+            print('Starting', args.type, 'service...')
+            start_server(loop, (args.host, args.port), service, local_ip)
+        else:
+            print('something went wrong')
+        
+        loop.run_forever()
diff --git a/sfc-py/service_function.py b/sfc-py/service_function.py
new file mode 100644 (file)
index 0000000..1293d24
--- /dev/null
@@ -0,0 +1,203 @@
+__author__ = "Jim Guichard"
+__copyright__ = "Copyright(c) 2014, Cisco Systems, Inc."
+__version__ = "0.1"
+__email__ = "[email protected]"
+__status__ = "alpha"
+
+#
+# Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v1.0 which accompanies this distribution,
+# and is available at http://www.eclipse.org/legal/epl-v10.html
+
+"""Network Service Header (NSH) Enabled Service Function"""
+
+import argparse
+import asyncio
+import sys
+import struct
+import socket
+import binascii
+from ctypes import *
+
+try:
+    import signal
+except ImportError:
+    signal = None
+
+
+class BASEHEADER(Structure):
+    _fields_ = [("version", c_ushort, 2),
+                ("flags", c_ushort, 8),
+                ("length", c_ushort, 6),
+                ("md_type", c_ubyte),
+                ("next_protocol", c_ubyte),
+                ("service_path", c_uint, 24),
+                ("service_index", c_uint, 8)]
+
+# Decode base NSH header
+
+base_values = BASEHEADER()
+
+
+class MyFwService:
+    def connection_made(self, transport):
+        self.transport = transport
+
+    def datagram_received(self, data, addr):
+        print('\nfw service received packet from SFF:\n', addr, binascii.hexlify(data))
+        rw_data = process_incoming_packet(data)
+        self.transport.sendto(rw_data, addr)
+        loop.stop()
+
+    def connection_refused(self, exc):
+        print('Connection refused:', exc)
+
+    def connection_lost(self, exc):
+        print('closing transport', exc)
+        loop = asyncio.get_event_loop()
+        loop.stop()
+
+
+class MyNatService:
+    def connection_made(self, transport):
+        self.transport = transport
+
+    def datagram_received(self, data, addr):
+        print('\nnat service received packet from SFF:\n', addr, binascii.hexlify(data))
+        print('\n')
+        rw_data = process_incoming_packet(data)
+        self.transport.sendto(rw_data, addr)
+        loop.stop()
+
+    def connection_refused(self, exc):
+        print('Connection refused:', exc)
+
+    def connection_lost(self, exc):
+        print('closing transport', exc)
+        loop = asyncio.get_event_loop()
+        loop.stop()
+
+
+class MyDpiService:
+    def connection_made(self, transport):
+        self.transport = transport
+
+    def datagram_received(self, data, addr):
+        print('\ndpi service received packet from SFF:\n', addr, binascii.hexlify(data))
+        print('\n')
+        rw_data = process_incoming_packet(data)
+        self.transport.sendto(rw_data, addr)
+        loop.stop()
+
+    def connection_refused(self, exc):
+        print('Connection refused:', exc)
+
+    def connection_lost(self, exc):
+        print('closing transport', exc)
+        loop = asyncio.get_event_loop()
+        loop.stop()
+
+
+def process_incoming_packet(data):
+    print('Processing recieved packet')
+    rw_data = bytearray(data)
+    decode_baseheader(data)
+    base_values.service_index -= 1
+    set_service_index(rw_data, base_values.service_index)
+    return rw_data
+
+
+def decode_baseheader(payload):
+    # Base Service header
+    base_header = payload[8:17]  # starts at offset 8 of payload
+
+    start_idx, base_values.md_type, base_values.next_protocol, path_idx = struct.unpack('!H B H I', base_header)
+
+    base_values.version = start_idx >> 14
+    base_values.flags = start_idx >> 6
+    base_values.length = start_idx >> 0
+    base_values.service_path = path_idx >> 8
+    base_values.service_index = path_idx & 0x000000FF
+
+    if __debug__ is False:
+        print ("\nBase NSH Header Decode:")
+        print (binascii.hexlify(base_header))
+        print ('NSH Version:', base_values.version)
+        print ('NSH base header flags:', base_values.flags)
+        print ('NSH base header length:', base_values.length)
+        print ('NSH MD-type:', base_values.md_type)
+        print ('NSH base header next protocol:', base_values.next_protocol)
+        print ('Service Path Identifier:', base_values.service_path)
+        print ('Service Index:', base_values.service_index)
+
+
+def set_service_index(rw_data, service_index):
+    rw_data[16] = service_index
+
+
+def start_server(loop, addr, service, myip):
+    t = asyncio.Task(loop.create_datagram_endpoint(
+        service, local_addr=(myip, 57444), remote_addr=addr))
+    loop.run_until_complete(t)
+    print('Connection made with SFF:', addr)
+
+
+def find_service(service):
+    if service == 'fw':
+        return MyFwService
+    elif service == 'nat':
+        return MyNatService
+    elif service == 'dpi':
+        return MyDpiService
+
+
+def get_service_ip():
+    # Let's find a local IP address to use as the source IP of client generated packets
+    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+    try:
+        s.connect(('8.8.8.8', 80))
+        client = (s.getsockname()[0])
+    except socket.error:
+        client = "Unknown IP"
+    finally:
+        s.close()
+    return client
+
+
+ARGS = argparse.ArgumentParser(description="NSH Service Function")
+ARGS.add_argument(
+    '--type', action="store", dest='type',
+    default=False, help='Run service function. Options: fw, nat, dpi')
+ARGS.add_argument(
+    '--host', action="store", dest='host',
+    default='127.0.0.1', help='SFF host name')
+ARGS.add_argument(
+    '--port', action="store", dest='port',
+    default=4789, type=int, help='SFF port number')
+
+if __name__ == '__main__':
+    args = ARGS.parse_args()
+    if ':' in args.host:
+        args.host, port = args.host.split(':', 1)
+        args.port = int(port)
+
+    if not args.type:
+        print('Please specify --type\n')
+        ARGS.print_help()
+    else:
+        loop = asyncio.get_event_loop()
+        if signal is not None:
+            loop.add_signal_handler(signal.SIGINT, loop.stop)
+
+        if '--type' in sys.argv:
+            # local_ip = get_service_ip()
+            local_ip = '127.0.0.1'
+            service = find_service(args.type)
+            print('Starting', args.type, 'service...')
+            start_server(loop, (args.host, args.port), service, local_ip)
+        else:
+            print('something went wrong')
+
+        loop.run_forever()
\ No newline at end of file
diff --git a/sfc-py/sff_new.py b/sfc-py/sff_new.py
new file mode 100644 (file)
index 0000000..a649e36
--- /dev/null
@@ -0,0 +1,401 @@
+__author__ = "Reinaldo Penno, Jim Guichard, Paul Quinn"
+__copyright__ = "Copyright(c) 2014, Cisco Systems, Inc."
+__version__ = "0.2"
+__status__ = "alpha"
+
+#
+# Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v1.0 which accompanies this distribution,
+# and is available at http://www.eclipse.org/legal/epl-v10.html
+
+"""Service Function Forwarder (SFF) server & client"""
+
+import argparse
+import asyncio
+import sys
+import struct
+import socket
+import binascii
+from ctypes import *
+
+try:
+    import signal
+except ImportError:
+    signal = None
+
+# Service Function Forwarder (SFF) table structure. Table referenced by Service Path Identifier as key. This table
+# contains all the service chains known to this SFF and lists an ordered set of service function types.
+
+sf_config_map = {'1': ['fw1', 'dpi1', 'nat1'], '2': ['fw2', 'dpi2', 'nat2'], '3': ['fw1', 'dpi1']}
+
+# Service Function Instance Registry.
+# This serves as a Service Function Registry referenced by service function type. A Service Function registers a
+# callback or an IP address/port combination.
+
+sf_map = {"fw1": {"function": "fw1_process_packet", "ip_address": "127.0.0.1", "port": "57444"},
+          "fw2": {"function": "", "ip_address": "", "port": ""},
+          "dpi1": {"function": "dpi1_process_packet", "ip_address": "", "port": "10000"},
+          "dpi2": {"function": "", "ip_address": "", "port": ""},
+          "nat1": {"function": "nat1_process_packet", "ip_address": "", "port": "10001"},
+          "nat2": {"function": "", "ip_address": "", "port": ""}}
+
+# VXLAN, NSH Base Header, and NSH Context Header data structures.
+
+class VXLANGPE(Structure):
+    _fields_ = [("flags", c_ubyte),
+                ("reserved", c_ubyte),
+                ("protocol_type", c_ushort),
+                ("vni", c_uint, 24),
+                ("reserved2", c_uint, 8)]
+
+
+class BASEHEADER(Structure):
+    _fields_ = [("version", c_ushort, 2),
+                ("flags", c_ushort, 8),
+                ("length", c_ushort, 6),
+                ("md_type", c_ubyte),
+                ("next_protocol", c_ubyte),
+                ("service_path", c_uint, 24),
+                ("service_index", c_uint, 8)]
+
+
+class CONTEXTHEADER(Structure):
+    _fields_ = [("network_platform", c_uint),
+                ("network_shared", c_uint),
+                ("service_platform", c_uint),
+                ("service_shared", c_uint)]
+
+# Global flags used for indication of current packet processing status.
+
+PACKET_CHAIN = 0b00000000  # Packet needs more processing within this SFF
+PACKET_CONSUMED = 0b00000001  # Packet was sent to another SFF or service function
+PACKET_ERROR = 0b00000010  # Packet will be dropped
+SERVICEFUNCTION_INVALID = 0xDEADBEEF  # Referenced service function is invalid
+
+# Client side code: Choose values for VXLAN, base NSH and context headers as part of packet generation
+
+vxlan_values = VXLANGPE(int('00000100', 2), 0, 0x894F, int('111111111111111111111111', 2), 64)
+ctx_values = CONTEXTHEADER(0xffffffff, 0, 0xffffffff, 0)
+base_values = BASEHEADER(0x1, int('01000000', 2), 0x6, 0x1, 0x1, 0x000001, 0x3)
+
+# Service side code: Store received values for VXLAN, base NSH and context headers data structures
+
+server_vxlan_values = VXLANGPE()
+server_ctx_values = CONTEXTHEADER()
+server_base_values = BASEHEADER()
+
+# Local service function callbacks. These are dummy services to track progress of packets through the service chain
+# at this SFF.
+
+
+def fw1_process_packet(data, addr):
+    print('fw1 processed packet from:', addr)
+    return PACKET_CHAIN
+
+
+def dpi1_process_packet(data, addr):
+    print('dpi1 processed packet from:', addr)
+    return PACKET_CHAIN
+
+
+def nat1_process_packet(data, addr):
+    print('nat1 processed packet from:', addr)
+    return PACKET_CHAIN
+
+
+# Client side code: Build NSH packet encapsulated in VXLAN & NSH.
+
+def build_packet():
+    # Build VXLAN header
+    vxlan_header = struct.pack('!B B H I', vxlan_values.flags, vxlan_values.reserved, vxlan_values.protocol_type,
+                               (vxlan_values.vni << 8) + vxlan_values.reserved2)
+    # Build base NSH header
+    base_header = struct.pack('!H B H I', (base_values.version << 14) + (base_values.flags << 6) + base_values.length,
+                              base_values.md_type,
+                              base_values.next_protocol, (base_values.service_path << 8) + base_values.service_index)
+    #Build NSH context headers
+    context_header = struct.pack('!I I I I', ctx_values.network_platform, ctx_values.network_shared,
+                                 ctx_values.service_platform, ctx_values.service_shared)
+    return vxlan_header + base_header + context_header
+
+
+# Decode the VXLAN header for a received packet at this SFF.
+
+def decode_vxlan(payload):
+    # VXLAN header
+    vxlan_header = payload[0:8]
+    server_vxlan_values.flags, server_vxlan_values.reserved, server_vxlan_values.protocol_type, \
+    vni_rsvd2 = struct.unpack('!B B H I', vxlan_header)
+
+    server_vxlan_values.vni = vni_rsvd2 >> 8;
+    server_vxlan_values.reserved2 = vni_rsvd2 & 0x000000FF
+
+    # Yes, it is weird but the comparison is against False. Display debug if started with -O option.
+    if __debug__ is False:
+        print("\nVXLAN Header Decode:")
+        print(binascii.hexlify(vxlan_header))
+        print('Flags:', server_vxlan_values.flags)
+        print('Reserved:', server_vxlan_values.reserved)
+        print('Protocol Type:', hex(int(server_vxlan_values.protocol_type)))
+        print('VNI:', server_vxlan_values.vni)
+        print('Reserved:', server_vxlan_values.reserved2)
+
+
+# Decode the NSH base header for a received packet at this SFF.
+
+def decode_baseheader(payload):
+    # Base Service header
+    base_header = payload[8:17]  # starts at offset 8 of payload
+
+    start_idx, server_base_values.md_type, server_base_values.next_protocol, path_idx = struct.unpack('!H B H I',
+                                                                                                      base_header)
+
+    server_base_values.version = start_idx >> 14
+    server_base_values.flags = start_idx >> 6
+    server_base_values.length = start_idx >> 0
+    server_base_values.service_path = path_idx >> 8;
+    server_base_values.service_index = path_idx & 0x000000FF
+
+    if __debug__ is False:
+        print ("\nBase NSH Header Decode:")
+        print (binascii.hexlify(base_header))
+        print ('NSH Version:', server_base_values.version)
+        print ('NSH base header flags:', server_base_values.flags)
+        print ('NSH base header length:', server_base_values.length)
+        print ('NSH MD-type:', server_base_values.md_type)
+        print ('NSH base header next protocol:', server_base_values.next_protocol)
+        print ('Service Path Identifier:', server_base_values.service_path)
+        print ('Service Index:', server_base_values.service_index)
+
+
+# Decode the NSH context headers for a received packet at this SFF.
+
+def decode_contextheader(payload):
+    # Context header
+    context_header = payload[17:33]
+
+    server_ctx_values.network_platform, server_ctx_values.network_shared, server_ctx_values.service_platform, server_ctx_values.service_shared = struct.unpack(
+        '!I I I I', context_header)
+
+    if __debug__ is False:
+        print ("\nNSH Context Header Decode:")
+        print (binascii.hexlify(context_header))
+        print ('First context header:', server_ctx_values.network_platform)
+        print ('Second context header:', server_ctx_values.network_shared)
+        print ('Third context header:', server_ctx_values.service_platform)
+        print ('Fourth context header:', server_ctx_values.service_shared)
+
+
+def lookup_next_sf(service_path, service_index):
+    next_sfi = SERVICEFUNCTION_INVALID
+    # First we determine the list of SFs in the received packet based on SPI value extracted from packet
+    try:
+        sf_list = sf_config_map[str(service_path)]
+    except KeyError as detail:
+        print('Apparently no valid SPI entry', detail)
+        # return SERVICEFUNCTION_INVALID to indicate no valid SPI entry
+        return next_sfi
+
+    if __debug__ is False:
+        print("\nSFI list for received SFP: ", sf_list)
+
+    # Now we determine the next SFI name
+    try:
+        next_sfi = sf_list[-service_index]
+        if __debug__ is False:
+            print("Next SF: ", next_sfi)
+    except IndexError as detail:
+        print('guess something went wrong - Error:', detail)
+    except:
+        print("Unexpected error:", sys.exc_info()[0])
+    finally:
+        return next_sfi
+
+
+def send_next_service(next_sfi, rw_data, addr):
+    # First we need to find if this SFI is internal to this Service Node
+    if next_sfi in sf_map:
+
+        if sf_map[next_sfi]['function'] != '' and sf_map[next_sfi]['ip_address'] == '':
+            functionp = globals()[sf_map[next_sfi]['function']]
+            address = sf_map[next_sfi]['ip_address']
+            ret = functionp(rw_data, addr)
+            if ret != PACKET_CONSUMED:
+                print('decrementing service index by 1 as packet processed by:', next_sfi)
+                server_base_values.service_index -= 1
+                set_service_index(rw_data, server_base_values.service_index)
+                print('current service index value:', server_base_values.service_index)
+            return ret, address
+        elif sf_map[next_sfi]['ip_address'] != '':
+            address = sf_map[next_sfi]['ip_address'], int(sf_map[next_sfi]['port'])
+            packet_status = PACKET_CONSUMED
+            print('were done - service', next_sfi, 'at address', address, 'consumed the packet')
+            return packet_status, address
+
+
+def set_service_index(rw_data, service_index):
+    rw_data[16] = service_index
+
+
+def process_incoming_packet(data, addr):
+    print("Processing packet from:", addr)
+    # Copy payload into bytearray so it can be changed
+    rw_data = bytearray(data)
+    # Decode the incoming packet for debug purposes and to strip out various header values
+    decode_vxlan(data)
+    decode_baseheader(data)
+    decode_contextheader(data)
+    # Lookup what to do with the packet based on Service Path Identifier (SPI)
+    print("\nLooking up received Service Path Identifier...")
+    packet_status = PACKET_CHAIN
+    address = ()
+    while packet_status == PACKET_CHAIN and server_base_values.service_index != 0:
+        next_sfi = lookup_next_sf(server_base_values.service_path, server_base_values.service_index)
+        if next_sfi == SERVICEFUNCTION_INVALID:
+            # bye, bye packet
+            print('we reached end of chain')
+            print('ended up with:', binascii.hexlify(rw_data))
+            print('service index end up as:', server_base_values.service_index)
+            rw_data.__init__()
+            data = ""
+            addr = ""
+            break
+        packet_status, address = send_next_service(next_sfi, rw_data, addr)
+
+    print('\nfinished processing packet from:', addr)
+    print('\nlistening for NSH packets ...')
+    return rw_data, address
+
+
+class MyUdpServer:
+    def connection_made(self, transport):
+        self.transport = transport
+
+    def datagram_received(self, data, addr):
+        print('Received packet from:', addr)
+        # Process the incoming packet
+        rw_data, address = process_incoming_packet(data, addr)
+        if address != '':
+            # send the packet to the next SFF based on address
+            self.transport.sendto(rw_data, address)
+        else:
+            # if want to echo packet back to client use following uncommented
+            self.transport.sendto(rw_data, addr)
+
+    def connection_refused(self, exc):
+        print('Connection refused:', exc)
+
+    def connection_lost(self, exc):
+        print('stop', exc)
+
+    def __init__(self, loop):
+        self.transport = None
+        self.loop = loop
+
+
+class MyUdpClient:
+    def connection_made(self, transport):
+        self.transport = transport
+        # Building client packet to send to SFF
+        packet = build_packet()
+        print('\nsending packet to SFF:\n', binascii.hexlify(packet))
+        # Send the packet
+        self.transport.sendto(packet)
+
+    def datagram_received(self, data, addr):
+        print('\nreceived packet from SFF:\n', binascii.hexlify(data))
+        print('\n')
+        # Decode all the headers
+        decode_vxlan(data)
+        decode_baseheader(data)
+        decode_contextheader(data)
+        self.loop.stop()
+
+    def connection_refused(self, exc):
+        print('Connection refused:', exc)
+
+    def connection_lost(self, exc):
+        print('closing transport', exc)
+        self.loop = asyncio.get_event_loop()
+        self.loop.stop()
+
+    def __init__(self, loop):
+        self.transport = None
+        self.loop = loop
+
+
+def start_server(loop, addr, udpserver):
+    t = asyncio.Task(loop.create_datagram_endpoint(
+        lambda: udpserver, local_addr=addr))
+    loop.run_until_complete(t)
+    print('\nStarting Service Function Forwarder (SFF)')
+    print('listening for NSH packets on port: ', addr[1])
+
+
+# note using port 57444 but could be any port, just remove port syntax and
+# update get_client_ip() to remove [0] from getsockname()
+def start_client(loop, addr, myip, udpclient):
+    t = asyncio.Task(loop.create_datagram_endpoint(
+        lambda: udpclient, local_addr=(myip, 57444), remote_addr=addr))
+    loop.run_until_complete(t)
+
+
+def get_client_ip():
+    # Let's find a local IP address to use as the source IP of client generated packets
+    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+    try:
+        s.connect(('8.8.8.8', 80))
+        client = (s.getsockname()[0])
+    except socket.error:
+        client = "Unknown IP"
+    finally:
+        s.close()
+    return client
+
+
+ARGS = argparse.ArgumentParser(description="UDP Echo example.")
+ARGS.add_argument(
+    '--server', action="store_true", dest='server',
+    default=False, help='Run udp server')
+ARGS.add_argument(
+    '--client', action="store_true", dest='client',
+    default=False, help='Run udp client')
+ARGS.add_argument(
+    '--host', action="store", dest='host',
+    default='127.0.0.1', help='Host name')
+ARGS.add_argument(
+    '--port', action="store", dest='port',
+    default=4789, type=int, help='Port number')
+
+
+def main():
+    args = ARGS.parse_args()
+    if ':' in args.host:
+        args.host, port = args.host.split(':', 1)
+        args.port = int(port)
+
+    if (not (args.server or args.client)) or (args.server and args.client):
+        print('Please specify --server or --client\n')
+        ARGS.print_help()
+    else:
+        loop = asyncio.get_event_loop()
+        # if signal is not None:
+        # loop.add_signal_handler(signal.SIGINT, loop.stop)
+
+        if '--server' in sys.argv:
+            udpserver = MyUdpServer(loop)
+            start_server(loop, (args.host, args.port), udpserver)
+        else:
+            # Figure out a source IP address / source UDP port for the client connection
+            udpclient = MyUdpClient(loop)
+            local_ip = get_client_ip()
+            start_client(loop, (args.host, args.port), local_ip, udpclient)
+
+        loop.run_forever()
+
+
+if __name__ == '__main__':
+    main()
index 1da4ce51f6c985bb1b2f3fb913a80dc88e5930c9..fc55881ba8c6b700c44af844da98eb6b5ab7373a 100644 (file)
@@ -59,6 +59,9 @@ SERVICE_FUNCTIONS_JSON = """
 }
 """
 
+
+
+
 SERVICE_FUNCTION_FORWARDERS_JSON = """
 {
   "service-function-forwarders": {
index 2b6cdbaf9c86f7cc43a29d4bb1989257d87b24e7..5a45a35bdf3fc677c5c15ca2bcd8ef533ad03ff2 100644 (file)
@@ -43,11 +43,11 @@ def delete_configuration():
         print ("=>Deleted all Service Function Forwarders \n")
     else:
         print ("=>Failure to delete SFFs, response code = {} \n". format(r.status_code))
-    r = s.delete(SFT_URL, stream=False, auth=(USERNAME, PASSWORD))
-    if r.status_code == 200:
-        print ("=>Deleted all Service Function Types \n")
-    else:
-        print ("=>Failure to delete SFTs, response code = {} \n". format(r.status_code))
+    r = s.delete(SFT_URL, stream=False, auth=(USERNAME, PASSWORD))
+    if r.status_code == 200:
+        print ("=>Deleted all Service Function Types \n")
+    else:
+        print ("=>Failure to delete SFTs, response code = {} \n". format(r.status_code))
     r = s.delete(SFP_URL, stream=False, auth=(USERNAME, PASSWORD))
     if r.status_code == 200:
         print ("=>Deleted all Service Function Paths \n")
@@ -75,7 +75,7 @@ def put_and_check(url, json_req, json_resp):
 if __name__ == "__main__":
     delete_configuration()
     put_and_check(SF_URL, SERVICE_FUNCTIONS_JSON, SERVICE_FUNCTIONS_JSON)
-    put_and_check(SFF_URL, SERVICE_FUNCTION_FORWARDERS_JSON, SERVICE_FUNCTION_FORWARDERS_JSON)
-    put_and_check(SFC_URL, SERVICE_CHAINS_JSON, SERVICE_CHAINS_JSON)
+    #put_and_check(SFF_URL, SERVICE_FUNCTION_FORWARDERS_JSON, SERVICE_FUNCTION_FORWARDERS_JSON)
+    #put_and_check(SFC_URL, SERVICE_CHAINS_JSON, SERVICE_CHAINS_JSON)
     #put_and_check(SFP_URL, SERVICE_PATH_JSON, SERVICE_PATH_RESP_JSON)