Commit de330c26 authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Tests - Mock IETF ACTN SDN Ctrl:

- Added skeleton for a test IETF SCTN SDN controller
parent 62f162cc
Loading
Loading
Loading
Loading
+150 −0
Original line number Diff line number Diff line
# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Mock MicroWave SDN controller
# -----------------------------
# REST server implementing minimal support for:
# - IETF YANG data model for Network Topology
#       Ref: https://www.rfc-editor.org/rfc/rfc8345.html
# - IETF YANG data model for Transport Network Client Signals
#       Ref: https://www.ietf.org/archive/id/draft-ietf-ccamp-client-signal-yang-07.html


# Ref: https://blog.miguelgrinberg.com/post/running-your-flask-application-over-https
# Ref: https://blog.miguelgrinberg.com/post/designing-a-restful-api-using-flask-restful

import functools, logging, sys, time
from flask import Flask, abort, jsonify, make_response, request
from flask_restful import Api, Resource

BIND_ADDRESS = '0.0.0.0'
BIND_PORT    = 8443
BASE_URL     = '/nmswebs/restconf/data'
STR_ENDPOINT = 'https://{:s}:{:s}{:s}'.format(str(BIND_ADDRESS), str(BIND_PORT), str(BASE_URL))
LOG_LEVEL    = logging.DEBUG

NETWORK_NODES = [
    {'node-id': '192.168.27.139', 'ietf-network-topology:termination-point': [
        {'tp-id': '1', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '2', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '3', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '4', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '5', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '6', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '10', 'ietf-te-topology:te': {'name': 'antena' }},
    ]},
    {'node-id': '192.168.27.140', 'ietf-network-topology:termination-point': [
        {'tp-id': '1', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '2', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '3', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '4', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '5', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '6', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '10', 'ietf-te-topology:te': {'name': 'antena' }},
    ]},
    {'node-id': '192.168.27.141', 'ietf-network-topology:termination-point': [
        {'tp-id': '1', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '2', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '3', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '4', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '5', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '6', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '10', 'ietf-te-topology:te': {'name': 'antena' }},
    ]},
    {'node-id': '192.168.27.142', 'ietf-network-topology:termination-point': [
        {'tp-id': '1', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '2', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '3', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '4', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '5', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '6', 'ietf-te-topology:te': {'name': 'ethernet'}},
        {'tp-id': '10', 'ietf-te-topology:te': {'name': 'antena' }},
    ]}
]
NETWORK_LINKS = [
    {   'link-id'    : '192.168.27.139:10--192.168.27.140:10',
        'source'     : {'source-node': '192.168.27.139', 'source-tp': '10'},
        'destination': {'dest-node'  : '192.168.27.140', 'dest-tp'  : '10'},
    },
    {   'link-id'    : '192.168.27.141:10--192.168.27.142:10',
        'source'     : {'source-node': '192.168.27.141', 'source-tp': '10'},
        'destination': {'dest-node'  : '192.168.27.142', 'dest-tp'  : '10'},
    }
]
NETWORK_SERVICES = {}


logging.basicConfig(level=LOG_LEVEL, format="[%(asctime)s] %(levelname)s:%(name)s:%(message)s")
LOGGER = logging.getLogger(__name__)

logging.getLogger('werkzeug').setLevel(logging.WARNING)

def log_request(logger : logging.Logger, response):
    timestamp = time.strftime('[%Y-%b-%d %H:%M]')
    logger.info('%s %s %s %s %s', timestamp, request.remote_addr, request.method, request.full_path, response.status)
    return response

class Health(Resource):
    def get(self):
        return make_response(jsonify({}), 200)

class Network(Resource):
    def get(self, network_uuid : str):
        if network_uuid != 'SIAE-ETH-TOPOLOGY': abort(400)
        network = {'node': NETWORK_NODES, 'ietf-network-topology:link': NETWORK_LINKS}
        return make_response(jsonify({'ietf-network:network': network}), 200)

class Services(Resource):
    def get(self):
        services = [service for service in NETWORK_SERVICES.values()]
        return make_response(jsonify({'ietf-eth-tran-service:etht-svc': {'etht-svc-instances': services}}), 200)

    def post(self):
        json_request = request.get_json()
        if not json_request: abort(400)
        if not isinstance(json_request, dict): abort(400)
        if 'etht-svc-instances' not in json_request: abort(400)
        json_services = json_request['etht-svc-instances']
        if not isinstance(json_services, list): abort(400)
        if len(json_services) != 1: abort(400)
        svc_data = json_services[0]
        etht_svc_name = svc_data['etht-svc-name']
        NETWORK_SERVICES[etht_svc_name] = svc_data
        return make_response(jsonify({}), 201)

class DelServices(Resource):
    def delete(self, service_uuid : str):
        NETWORK_SERVICES.pop(service_uuid, None)
        return make_response(jsonify({}), 204)

def main():
    LOGGER.info('Starting...')
    
    app = Flask(__name__)
    app.after_request(functools.partial(log_request, LOGGER))

    api = Api(app, prefix=BASE_URL)
    api.add_resource(Health,      '/ietf-network:networks')
    api.add_resource(Network,     '/ietf-network:networks/network=<string:network_uuid>')
    api.add_resource(Services,    '/ietf-eth-tran-service:etht-svc')
    api.add_resource(DelServices, '/ietf-eth-tran-service:etht-svc/etht-svc-instances=<string:service_uuid>')

    LOGGER.info('Listening on {:s}...'.format(str(STR_ENDPOINT)))
    app.run(debug=True, host=BIND_ADDRESS, port=BIND_PORT, ssl_context='adhoc')

    LOGGER.info('Bye')
    return 0

if __name__ == '__main__':
    sys.exit(main())
+53 −0
Original line number Diff line number Diff line
# Mock IETF ACTN SDN Controller

This REST server implements very basic support for the following YANG data models:
- IETF YANG Data Model for Transport Network Client Signals (draft-ietf-ccamp-client-signal-yang-10)
  - Ref: https://datatracker.ietf.org/doc/draft-ietf-ccamp-client-signal-yang/
- IETF YANG Data Model for Traffic Engineering Tunnels, Label Switched Paths and Interfaces (draft-ietf-teas-yang-te-34)
  - Ref: https://datatracker.ietf.org/doc/draft-ietf-teas-yang-te/

The aim of this server is to enable testing the IetfActnDeviceDriver and the IetfActnServiceHandler.
Follow the steps below to perform the test:

## 1. Deploy TeraFlowSDN controller and the scenario
Deploy the test scenario "ietf_actn_deploy.sh":
```bash
source src/tests/tools/mock_ietf_actn_sdn_ctrl/scenario/ietf_actn_deploy.sh
./deploy/all.sh
```

## 2. Install requirements and run the Mock IETF ACTN SDN controller
__NOTE__: if you run the Mock IETF ACTN SDN controller from the PyEnv used for developping on the TeraFlowSDN framework,
all the requirements are already in place. Install them only if you execute it in a separate/standalone environment.

Install the required dependencies as follows:
```bash
pip install Flask==2.1.3 Flask-RESTful==0.3.9
```

Run the Mock IETF ACTN SDN Controller as follows:
```bash
python src/tests/tools/mock_ietf_actn_sdn_ctrl/MockIetfActnSdnCtrl.py
```

## 3. Deploy the test descriptors
Edit the descriptors to meet your environment specifications.
Edit "network_descriptors.json" and change IP address and port of the IETF ACTN SDN controller of the "ACTN" device.
- Set value of config rule "_connect/address" to the address of the host where the Mock IETF ACTN SDN controller is
  running (default="192.168.1.1").
- Set value of config rule "_connect/port" to the port where your Mock IETF ACTN SDN controller is listening on
  (default="8443").

Upload the "network_descriptors.json" through the TeraFlowSDN WebUI.
- If not already selected, select Context(admin)/Topology(admin).
- Check that a network topology with 4 routers + 1 IETF ACTN radio system are loaded. They should form 2 rings.

Upload the "service_descriptor.json" through the TeraFlowSDN WebUI.
- Check that 2 services have been created.
- The "actn-svc" should have a connection and be supported by a sub-service.
- The sub-service should also have a connection.
- The R1, R3, and MW devices should have configuration rules established.

# 4. Delete the IETF ACTN service
Find the "mw-svc" on the WebUI, navigate to its details, and delete the service pressing the "Delete Service" button.
The service, sub-service, and device configuration rules should be removed.
+16 −0
Original line number Diff line number Diff line
#!/usr/bin/env bash
# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

python MockIetfActnSdnCtrl.py
+36 −0
Original line number Diff line number Diff line
# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Set the URL of your local Docker registry where the images will be uploaded to.
export TFS_REGISTRY_IMAGE="http://localhost:32000/tfs/"

# Set the list of components, separated by spaces, you want to build images for, and deploy.
# Supported components are:
#   context device ztp policy service nbi monitoring webui
#   interdomain slice pathcomp dlt
#   dbscanserving opticalattackmitigator opticalattackdetector
#   l3_attackmitigator l3_centralizedattackdetector l3_distributedattackdetector
export TFS_COMPONENTS="context device pathcomp service slice webui"

# Set the tag you want to use for your images.
export TFS_IMAGE_TAG="dev"

# Set the name of the Kubernetes namespace to deploy to.
export TFS_K8S_NAMESPACE="tfs"

# Set additional manifest files to be applied after the deployment
export TFS_EXTRA_MANIFESTS="manifests/nginx_ingress_http.yaml"

# Set the neew Grafana admin password
export TFS_GRAFANA_PASSWORD="admin123+"
+117 −0
Original line number Diff line number Diff line
{
    "contexts": [
        {
            "context_id": {"context_uuid": {"uuid": "admin"}},
            "topology_ids": [],
            "service_ids": []
        }
    ],
    "topologies": [
        {
            "topology_id": {"topology_uuid": {"uuid": "admin"}, "context_id": {"context_uuid": {"uuid": "admin"}}},
            "device_ids": [],
            "link_ids": []
        }
    ],
    "devices": [
        {
            "device_id": {"device_uuid": {"uuid": "R1"}}, "device_type": "emu-packet-router", "device_drivers": [0],
            "device_operational_status": 2, "device_endpoints": [],
            "device_config": {"config_rules": [
                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
                    {"type": "copper", "uuid": "MW", "sample_types": []},
                    {"type": "copper", "uuid": "R2", "sample_types": []},
                    {"type": "copper", "uuid": "EXT", "sample_types": []}
                ]}}}
            ]}
        },
        {
            "device_id": {"device_uuid": {"uuid": "R2"}}, "device_type": "emu-packet-router", "device_drivers": [0],
            "device_operational_status": 2, "device_endpoints": [],
            "device_config": {"config_rules": [
                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
                    {"type": "copper", "uuid": "MW", "sample_types": []},
                    {"type": "copper", "uuid": "R1", "sample_types": []},
                    {"type": "copper", "uuid": "EXT", "sample_types": []}
                ]}}}
            ]}
        },
        {
            "device_id": {"device_uuid": {"uuid": "R3"}}, "device_type": "emu-packet-router", "device_drivers": [0],
            "device_operational_status": 2, "device_endpoints": [],
            "device_config": {"config_rules": [
                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
                    {"type": "copper", "uuid": "MW", "sample_types": []},
                    {"type": "copper", "uuid": "R4", "sample_types": []},
                    {"type": "copper", "uuid": "EXT", "sample_types": []}
                ]}}}
            ]}
        },
        {
            "device_id": {"device_uuid": {"uuid": "R4"}}, "device_type": "emu-packet-router", "device_drivers": [0],
            "device_operational_status": 2, "device_endpoints": [],
            "device_config": {"config_rules": [
                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
                    {"type": "copper", "uuid": "MW", "sample_types": []},
                    {"type": "copper", "uuid": "R3", "sample_types": []},
                    {"type": "copper", "uuid": "EXT", "sample_types": []}
                ]}}}
            ]}
        },
        {
            "device_id": {"device_uuid": {"uuid": "MW"}}, "device_type": "microwave-radio-system", "device_drivers": [4],
            "device_operational_status": 2, "device_endpoints": [],
            "device_config": {"config_rules": [
                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "192.168.1.1"}},
                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}},
                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"timeout": 120}}}
            ]}
        }
    ],
    "links": [
        {
            "link_id": {"link_uuid": {"uuid": "R1/R2==R2/R1"}}, "link_endpoint_ids": [
                {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "R2"}},
                {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "R1"}}
            ]
        },
        {
            "link_id": {"link_uuid": {"uuid": "R3/R4==R4/R3"}}, "link_endpoint_ids": [
                {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "R4"}},
                {"device_id": {"device_uuid": {"uuid": "R4"}}, "endpoint_uuid": {"uuid": "R3"}}
            ]
        },
        {
            "link_id": {"link_uuid": {"uuid": "R1/MW==MW/172.18.0.1:1"}}, "link_endpoint_ids": [
                {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "MW"}},
                {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "172.18.0.1:1"}}
            ]
        },
        {
            "link_id": {"link_uuid": {"uuid": "R2/MW==MW/172.18.0.2:1"}}, "link_endpoint_ids": [
                {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "MW"}},
                {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "172.18.0.2:1"}}
            ]
        },
        {
            "link_id": {"link_uuid": {"uuid": "R3/MW==MW/172.18.0.3:1"}}, "link_endpoint_ids": [
                {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "MW"}},
                {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "172.18.0.3:1"}}
            ]
        },
        {
            "link_id": {"link_uuid": {"uuid": "R4/MW==MW/172.18.0.4:1"}}, "link_endpoint_ids": [
                {"device_id": {"device_uuid": {"uuid": "R4"}}, "endpoint_uuid": {"uuid": "MW"}},
                {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "172.18.0.4:1"}}
            ]
        }
    ]
}
 No newline at end of file
Loading