Skip to content
routes.py 10.3 KiB
Newer Older
# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
#
# 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.
Lucie LONG's avatar
Lucie LONG committed

Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
import json, logging
from flask import jsonify, redirect, render_template, Blueprint, flash, session, url_for, request
Lucie LONG's avatar
Lucie LONG committed
from common.proto.context_pb2 import Connection, Context, Device, Empty, Link, Service, Slice, Topology, ContextIdList
Lucie LONG's avatar
Lucie LONG committed
from common.tools.grpc.Tools import grpc_message_to_json_string
from context.client.ContextClient import ContextClient
from device.client.DeviceClient import DeviceClient
Lucie LONG's avatar
Lucie LONG committed
from service.client.ServiceClient import ServiceClient
Lucie LONG's avatar
Lucie LONG committed
from slice.client.SliceClient import SliceClient
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
from webui.service.main.DescriptorTools import (
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
    format_custom_config_rules, get_descriptors_add_contexts, get_descriptors_add_services, get_descriptors_add_slices,
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
    get_descriptors_add_topologies, split_devices_by_rules)
from webui.service.main.forms import ContextForm, DescriptorForm
Lucie LONG's avatar
Lucie LONG committed

main = Blueprint('main', __name__)
Lucie LONG's avatar
Lucie LONG committed

context_client = ContextClient()
device_client = DeviceClient()
Lucie LONG's avatar
Lucie LONG committed
service_client = ServiceClient()
Lucie LONG's avatar
Lucie LONG committed
slice_client = SliceClient()

logger = logging.getLogger(__name__)
Lucie LONG's avatar
Lucie LONG committed

Lucie LONG's avatar
Lucie LONG committed
ENTITY_TO_TEXT = {
    # name   => singular,    plural
Lucie LONG's avatar
Lucie LONG committed
    'context'   : ('Context',    'Contexts'   ),
    'topology'  : ('Topology',   'Topologies' ),
    'device'    : ('Device',     'Devices'    ),
    'link'      : ('Link',       'Links'      ),
    'service'   : ('Service',    'Services'   ),
    'slice'     : ('Slice',      'Slices'     ),
    'connection': ('Connection', 'Connections'),
Lucie LONG's avatar
Lucie LONG committed
}
Lucie LONG's avatar
Lucie LONG committed

Lucie LONG's avatar
Lucie LONG committed
ACTION_TO_TEXT = {
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
    # action =>  infinitive,  past
    'add'     : ('Add',       'Added'),
    'update'  : ('Update',    'Updated'),
    'config'  : ('Configure', 'Configured'),
Lucie LONG's avatar
Lucie LONG committed
}
Lucie LONG's avatar
Lucie LONG committed

Lucie LONG's avatar
Lucie LONG committed
def process_descriptor(entity_name, action_name, grpc_method, grpc_class, entities):
    entity_name_singluar,entity_name_plural = ENTITY_TO_TEXT[entity_name]
    action_infinitive, action_past = ACTION_TO_TEXT[action_name]
Lucie LONG's avatar
Lucie LONG committed
    for entity in entities:
Lucie LONG's avatar
Lucie LONG committed
            grpc_method(grpc_class(**entity))
            num_ok += 1
        except Exception as e: # pylint: disable=broad-except
Lucie LONG's avatar
Lucie LONG committed
            flash(f'Unable to {action_infinitive} {entity_name_singluar} {str(entity)}: {str(e)}', 'error')
Lucie LONG's avatar
Lucie LONG committed
    if num_ok : flash(f'{str(num_ok)} {entity_name_plural} {action_past}', 'success')
    if num_err: flash(f'{str(num_err)} {entity_name_plural} failed', 'danger')
Lucie LONG's avatar
Lucie LONG committed

def process_descriptors(descriptors):
    try:
        descriptors_file = request.files[descriptors.name]
        descriptors_data = descriptors_file.read()
        descriptors = json.loads(descriptors_data)
    except Exception as e: # pylint: disable=broad-except
        flash(f'Unable to load descriptor file: {str(e)}', 'danger')
        return
Lucie LONG's avatar
Lucie LONG committed

    dummy_mode  = descriptors.get('dummy_mode' , False)
    contexts    = descriptors.get('contexts'   , [])
    topologies  = descriptors.get('topologies' , [])
    devices     = descriptors.get('devices'    , [])
    links       = descriptors.get('links'      , [])
    services    = descriptors.get('services'   , [])
    slices      = descriptors.get('slices'     , [])
    connections = descriptors.get('connections', [])

Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
    # Format CustomConfigRules in Devices, Services and Slices provided in JSON format
    for device in devices:
        config_rules = device.get('device_config', {}).get('config_rules', [])
        config_rules = format_custom_config_rules(config_rules)
        device['device_config']['config_rules'] = config_rules

    for service in services:
        config_rules = service.get('service_config', {}).get('config_rules', [])
        config_rules = format_custom_config_rules(config_rules)
        service['service_config']['config_rules'] = config_rules

    for slice in slices:
        config_rules = slice.get('slice_config', {}).get('config_rules', [])
        config_rules = format_custom_config_rules(config_rules)
        slice['slice_config']['config_rules'] = config_rules


Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
    # Context and Topology require to create the entity first, and add devices, links, services, slices, etc. in a
    # second stage.
    contexts_add = get_descriptors_add_contexts(contexts)
    topologies_add = get_descriptors_add_topologies(topologies)

Lucie LONG's avatar
Lucie LONG committed
    if dummy_mode:
        # Dummy Mode: used to pre-load databases (WebUI debugging purposes) with no smart or automated tasks.
        context_client.connect()
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
        process_descriptor('context',    'add',    context_client.SetContext,    Context,    contexts_add  )
        process_descriptor('topology',   'add',    context_client.SetTopology,   Topology,   topologies_add)
        process_descriptor('device',     'add',    context_client.SetDevice,     Device,     devices       )
        process_descriptor('link',       'add',    context_client.SetLink,       Link,       links         )
        process_descriptor('service',    'add',    context_client.SetService,    Service,    services      )
        process_descriptor('slice',      'add',    context_client.SetSlice,      Slice,      slices        )
Lucie LONG's avatar
Lucie LONG committed
        process_descriptor('connection', 'add',    context_client.SetConnection, Connection, connections   )
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
        process_descriptor('context',    'update', context_client.SetContext,    Context,    contexts      )
        process_descriptor('topology',   'update', context_client.SetTopology,   Topology,   topologies    )
Lucie LONG's avatar
Lucie LONG committed
        context_client.close()
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
    else:
        # Normal mode: follows the automated workflows in the different components
        assert len(connections) == 0, 'in normal mode, connections should not be set'
Lucie LONG's avatar
Lucie LONG committed

Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
        # Device, Service and Slice require to first create the entity and the configure it
        devices_add, devices_config = split_devices_by_rules(devices)
        services_add = get_descriptors_add_services(services)
        slices_add = get_descriptors_add_slices(slices)
Lucie LONG's avatar
Lucie LONG committed

Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
        context_client.connect()
        device_client.connect()
        service_client.connect()
        slice_client.connect()

        process_descriptor('context',    'add',    context_client.SetContext,      Context,    contexts_add  )
        process_descriptor('topology',   'add',    context_client.SetTopology,     Topology,   topologies_add)
        process_descriptor('device',     'add',    device_client .AddDevice,       Device,     devices_add   )
        process_descriptor('device',     'config', device_client .ConfigureDevice, Device,     devices_config)
        process_descriptor('link',       'add',    context_client.SetLink,         Link,       links         )
        process_descriptor('service',    'add',    service_client.CreateService,   Service,    services_add  )
        process_descriptor('service',    'update', service_client.UpdateService,   Service,    services      )
        process_descriptor('slice',      'add',    slice_client  .CreateSlice,     Slice,      slices_add    )
        process_descriptor('slice',      'update', slice_client  .UpdateSlice,     Slice,      slices        )
        process_descriptor('context',    'update', context_client.SetContext,      Context,    contexts      )
        process_descriptor('topology',   'update', context_client.SetTopology,     Topology,   topologies    )

        slice_client.close()
        service_client.close()
        device_client.close()
        context_client.close()
Lucie LONG's avatar
Lucie LONG committed

@main.route('/', methods=['GET', 'POST'])
def home():
    context_client.connect()
    response: ContextIdList = context_client.ListContextIds(Empty())
    context_form: ContextForm = ContextForm()
    context_form.context.choices.append(('', 'Select...'))
Lucie LONG's avatar
Lucie LONG committed

    for context in response.context_ids:
        context_form.context.choices.append((context.context_uuid.uuid, context.context_uuid))
Lucie LONG's avatar
Lucie LONG committed

    if context_form.validate_on_submit():
        session['context_uuid'] = context_form.context.data
        flash(f'The context was successfully set to `{context_form.context.data}`.', 'success')
        return redirect(url_for("main.home"))
Lucie LONG's avatar
Lucie LONG committed

    if 'context_uuid' in session:
        context_form.context.data = session['context_uuid']
Lucie LONG's avatar
Lucie LONG committed

    descriptor_form: DescriptorForm = DescriptorForm()
    try:
        if descriptor_form.validate_on_submit():
            process_descriptors(descriptor_form.descriptors)
            return redirect(url_for("main.home"))
    except Exception as e:
        logger.exception('Descriptor load failed')
        flash(f'Descriptor load failed: `{str(e)}`', 'danger')
    finally:
        context_client.close()
        device_client.close()
Lucie LONG's avatar
Lucie LONG committed

    return render_template('main/home.html', context_form=context_form, descriptor_form=descriptor_form)
Lucie LONG's avatar
Lucie LONG committed

@main.route('/topology', methods=['GET'])
def topology():
    context_client.connect()
    try:
        response = context_client.ListDevices(Empty())
        devices = [{
            'id': device.device_id.device_uuid.uuid,
            'name': device.device_id.device_uuid.uuid,
            'type': device.device_type,
        } for device in response.devices]
Lucie LONG's avatar
Lucie LONG committed

        response = context_client.ListLinks(Empty())
Lucie LONG's avatar
Lucie LONG committed
        links = []
        for link in response.links:
            if len(link.link_endpoint_ids) != 2:
                str_link = grpc_message_to_json_string(link)
                logger.warning('Unexpected link with len(endpoints) != 2: {:s}'.format(str_link))
                continue
            links.append({
                'id': link.link_id.link_uuid.uuid,
                'source': link.link_endpoint_ids[0].device_id.device_uuid.uuid,
                'target': link.link_endpoint_ids[1].device_id.device_uuid.uuid,
            })
Lucie LONG's avatar
Lucie LONG committed

        return jsonify({'devices': devices, 'links': links})
    except:
        logger.exception('Error retrieving topology')
    finally:
        context_client.close()
Lucie LONG's avatar
Lucie LONG committed

@main.get('/about')
def about():
    return render_template('main/about.html')
Lucie LONG's avatar
Lucie LONG committed

Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
@main.get('/debug')
def debug():
    return render_template('main/debug.html')
Lucie LONG's avatar
Lucie LONG committed

@main.get('/resetsession')
def reset_session():
    session.clear()
Lucie LONG's avatar
Lucie LONG committed
    return redirect(url_for("main.home"))