Commit 4d3315aa authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

WebUI component:

- added validation entities exist
- added missing constraint rendering statements
- minor template improvements
- code cleanup
parent 24776f2c
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -96,6 +96,7 @@ def create_app(use_config=None, web_app_root=None):
    app.register_blueprint(link)

    app.jinja_env.globals.update({              # pylint: disable=no-member
        'enumerate'           : enumerate,
        'json_to_list'        : json_to_list,
        'get_working_context' : get_working_context,
        'get_working_topology': get_working_topology,
+4 −9
Original line number Diff line number Diff line
@@ -12,21 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# external imports
from flask_wtf import FlaskForm
from wtforms import StringField, SelectField, TextAreaField, SubmitField, BooleanField, Form
from wtforms.validators import DataRequired, Length, NumberRange, Regexp, ValidationError
from wtforms import StringField, SelectField, TextAreaField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Length, NumberRange, ValidationError
from common.proto.context_pb2 import DeviceOperationalStatusEnum
from webui.utils.form_validators import key_value_validator

class AddDeviceForm(FlaskForm):
    device_id = StringField('ID', 
                           validators=[DataRequired(), Length(min=5)])
    device_type = SelectField('Type', choices = [])                                                     
    operational_status = SelectField('Operational Status',
                        #    choices=[(-1, 'Select...'), (0, 'Undefined'), (1, 'Disabled'), (2, 'Enabled')],
                           coerce=int,
                           validators=[NumberRange(min=0)])
    device_type = SelectField('Type')
    operational_status = SelectField('Operational Status', coerce=int, validators=[NumberRange(min=0)])
    device_drivers_undefined = BooleanField('UNDEFINED / EMULATED')
    device_drivers_openconfig = BooleanField('OPENCONFIG')
    device_drivers_transport_api = BooleanField('TRANSPORT_API')
+55 −51
Original line number Diff line number Diff line
@@ -14,16 +14,14 @@

import json
from flask import current_app, render_template, Blueprint, flash, session, redirect, url_for
from common.DeviceTypes import DeviceTypeEnum
from common.proto.context_pb2 import (
    ConfigActionEnum, Device, DeviceDriverEnum, DeviceId, DeviceList, DeviceOperationalStatusEnum, Empty, TopologyId)
from common.tools.object_factory.Context import json_context_id
from common.tools.object_factory.Topology import json_topology_id
    ConfigActionEnum, Device, DeviceDriverEnum, DeviceId, DeviceList, DeviceOperationalStatusEnum, Empty)
from common.tools.context_queries.Device import get_device
from common.tools.context_queries.Topology import get_topology
from context.client.ContextClient import ContextClient
from device.client.DeviceClient import DeviceClient
from webui.service.device.forms import AddDeviceForm
from common.DeviceTypes import DeviceTypeEnum
from webui.service.device.forms import ConfigForm
from webui.service.device.forms import UpdateDeviceForm
from webui.service.device.forms import AddDeviceForm, ConfigForm, UpdateDeviceForm

device = Blueprint('device', __name__, url_prefix='/device')
context_client = ContextClient()
@@ -39,16 +37,18 @@ def home():
    topology_uuid = session['topology_uuid']

    context_client.connect()
    json_topo_id = json_topology_id(topology_uuid, context_id=json_context_id(context_uuid))
    grpc_topology = context_client.GetTopology(TopologyId(**json_topo_id))
    grpc_topology = get_topology(context_client, topology_uuid, context_uuid=context_uuid, rw_copy=False)
    if grpc_topology is None:
        flash('Context({:s})/Topology({:s}) not found'.format(str(context_uuid), str(topology_uuid)), 'danger')
        devices = []
    else:
        topo_device_uuids = {device_id.device_uuid.uuid for device_id in grpc_topology.device_ids}
        grpc_devices: DeviceList = context_client.ListDevices(Empty())
    context_client.close()

        devices = [
            device for device in grpc_devices.devices
            if device.device_id.device_uuid.uuid in topo_device_uuids
        ]
    context_client.close()

    return render_template(
        'device/home.html', devices=devices, dde=DeviceDriverEnum,
@@ -71,23 +71,23 @@ def add():
    if form.validate_on_submit():
        device_obj = Device()
        # Device UUID: 
        device_obj.device_id.device_uuid.uuid = form.device_id.data
        device_obj.device_id.device_uuid.uuid = form.device_id.data # pylint: disable=no-member

        # Device type: 
        device_obj.device_type = str(form.device_type.data)

        # Device configurations: 
        config_rule = device_obj.device_config.config_rules.add()
        config_rule = device_obj.device_config.config_rules.add() # pylint: disable=no-member
        config_rule.action = ConfigActionEnum.CONFIGACTION_SET
        config_rule.custom.resource_key = '_connect/address'
        config_rule.custom.resource_value = form.device_config_address.data

        config_rule = device_obj.device_config.config_rules.add()
        config_rule = device_obj.device_config.config_rules.add() # pylint: disable=no-member
        config_rule.action = ConfigActionEnum.CONFIGACTION_SET
        config_rule.custom.resource_key = '_connect/port'
        config_rule.custom.resource_value = form.device_config_port.data

        config_rule = device_obj.device_config.config_rules.add()
        config_rule = device_obj.device_config.config_rules.add() # pylint: disable=no-member
        config_rule.action = ConfigActionEnum.CONFIGACTION_SET
        config_rule.custom.resource_key = '_connect/settings'

@@ -105,20 +105,22 @@ def add():
        device_obj.device_operational_status = form.operational_status.data

        # Device drivers: 
        device_drivers = list()
        if form.device_drivers_undefined.data:
            device_obj.device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_UNDEFINED)
            device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_UNDEFINED)
        if form.device_drivers_openconfig.data:
            device_obj.device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG)
            device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG)
        if form.device_drivers_transport_api.data:
            device_obj.device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API)
            device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API)
        if form.device_drivers_p4.data:
            device_obj.device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_P4)
            device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_P4)
        if form.device_drivers_ietf_network_topology.data:
            device_obj.device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY)
            device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY)
        if form.device_drivers_onf_tr_352.data:
            device_obj.device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352)
            device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352)
        if form.device_drivers_xr.data:
            device_obj.device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_XR)
            device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_XR)
        device_obj.device_drivers.extend(device_drivers) # pylint: disable=no-member

        try:
            device_client.connect()
@@ -126,7 +128,7 @@ def add():
            device_client.close()
            flash(f'New device was created with ID "{response.device_uuid.uuid}".', 'success')
            return redirect(url_for('device.home'))
        except Exception as e:
        except Exception as e: # pylint: disable=broad-except
            flash(f'Problem adding the device. {e.details()}', 'danger')
        
    return render_template('device/add.html', form=form,
@@ -134,14 +136,15 @@ def add():

@device.route('detail/<path:device_uuid>', methods=['GET', 'POST'])
def detail(device_uuid: str):
    request = DeviceId()
    request.device_uuid.uuid = device_uuid
    context_client.connect()
    response = context_client.GetDevice(request)
    device_obj = get_device(context_client, device_uuid, rw_copy=False)
    if device_obj is None:
        flash('Device({:s}) not found'.format(str(device_uuid)), 'danger')
        device_obj = Device()
    context_client.close()
    return render_template('device/detail.html', device=response,
                                                 dde=DeviceDriverEnum,
                                                 dose=DeviceOperationalStatusEnum)

    return render_template(
        'device/detail.html', device=device_obj, dde=DeviceDriverEnum, dose=DeviceOperationalStatusEnum)

@device.get('<path:device_uuid>/delete')
def delete(device_uuid):
@@ -154,13 +157,13 @@ def delete(device_uuid):
        # TODO: finalize implementation

        request = DeviceId()
        request.device_uuid.uuid = device_uuid
        request.device_uuid.uuid = device_uuid # pylint: disable=no-member
        device_client.connect()
        response = device_client.DeleteDevice(request)
        device_client.DeleteDevice(request)
        device_client.close()

        flash(f'Device "{device_uuid}" deleted successfully!', 'success')
    except Exception as e:
    except Exception as e: # pylint: disable=broad-except
        flash(f'Problem deleting device "{device_uuid}": {e.details()}', 'danger')
        current_app.logger.exception(e)
    return redirect(url_for('device.home'))
@@ -169,25 +172,25 @@ def delete(device_uuid):
def addconfig(device_uuid):
    form = ConfigForm()
    request = DeviceId()
    request.device_uuid.uuid = device_uuid
    request.device_uuid.uuid = device_uuid # pylint: disable=no-member
    context_client.connect()
    response = context_client.GetDevice(request)
    context_client.close()

    if form.validate_on_submit():
        device = Device()
        device.CopyFrom(response)
        config_rule = device.device_config.config_rules.add()
        device_obj = Device()
        device_obj.CopyFrom(response)
        config_rule = device_obj.device_config.config_rules.add() # pylint: disable=no-member
        config_rule.action = ConfigActionEnum.CONFIGACTION_SET
        config_rule.custom.resource_key = form.device_key_config.data
        config_rule.custom.resource_value = form.device_value_config.data
        try:
            device_client.connect()
            response: DeviceId = device_client.ConfigureDevice(device)
            response: DeviceId = device_client.ConfigureDevice(device_obj)
            device_client.close()
            flash(f'New configuration was created with ID "{response.device_uuid.uuid}".', 'success')
            return redirect(url_for('device.home'))
        except Exception as e:
        except Exception as e: # pylint: disable=broad-except
             flash(f'Problem adding the device. {e.details()}', 'danger')

    return render_template('device/addconfig.html', form=form,  submit_text='Add New Configuration')
@@ -203,28 +206,29 @@ def updateconfig():
def update(device_uuid):
    form = UpdateDeviceForm()
    request = DeviceId()
    request.device_uuid.uuid = device_uuid
    request.device_uuid.uuid = device_uuid # pylint: disable=no-member
    context_client.connect()
    response = context_client.GetDevice(request)
    context_client.close()

    # listing enum values
    form.update_operational_status.choices = []
    for key, value in DeviceOperationalStatusEnum.DESCRIPTOR.values_by_name.items():
        form.update_operational_status.choices.append((DeviceOperationalStatusEnum.Value(key), key.replace('DEVICEOPERATIONALSTATUS_', '')))
    for key, _ in DeviceOperationalStatusEnum.DESCRIPTOR.values_by_name.items():
        item = (DeviceOperationalStatusEnum.Value(key), key.replace('DEVICEOPERATIONALSTATUS_', ''))
        form.update_operational_status.choices.append(item)

    form.update_operational_status.default = response.device_operational_status

    if form.validate_on_submit():
        device = Device()
        device.CopyFrom(response)
        device.device_operational_status = form.update_operational_status.data
        device_obj = Device()
        device_obj.CopyFrom(response)
        device_obj.device_operational_status = form.update_operational_status.data
        try:
            device_client.connect()
            response: DeviceId = device_client.ConfigureDevice(device)
            response: DeviceId = device_client.ConfigureDevice(device_obj)
            device_client.close()
            flash(f'Status of device with ID "{response.device_uuid.uuid}" was updated.', 'success')
            return redirect(url_for('device.home'))
        except Exception as e:
        except Exception as e: # pylint: disable=broad-except
             flash(f'Problem updating the device. {e.details()}', 'danger')  
    return render_template('device/update.html', device=response, form=form, submit_text='Update Device')
+25 −21
Original line number Diff line number Diff line
@@ -14,10 +14,10 @@


from flask import render_template, Blueprint, flash, session, redirect, url_for
from common.proto.context_pb2 import Empty, LinkId, LinkList, TopologyId
from common.proto.context_pb2 import Empty, Link, LinkList
from common.tools.context_queries.EndPoint import get_endpoint_names
from common.tools.object_factory.Context import json_context_id
from common.tools.object_factory.Topology import json_topology_id
from common.tools.context_queries.Link import get_link
from common.tools.context_queries.Topology import get_topology
from context.client.ContextClient import ContextClient


@@ -33,19 +33,20 @@ def home():
    context_uuid = session['context_uuid']
    topology_uuid = session['topology_uuid']

    links, endpoint_ids = list(), list()
    device_names, endpoints_data = dict(), dict()

    context_client.connect()
    json_topo_id = json_topology_id(topology_uuid, context_id=json_context_id(context_uuid))
    grpc_topology = context_client.GetTopology(TopologyId(**json_topo_id))
    grpc_topology = get_topology(context_client, topology_uuid, context_uuid=context_uuid, rw_copy=False)
    if grpc_topology is None:
        flash('Context({:s})/Topology({:s}) not found'.format(str(context_uuid), str(topology_uuid)), 'danger')
    else:
        topo_link_uuids = {link_id.link_uuid.uuid for link_id in grpc_topology.link_ids}
        grpc_links: LinkList = context_client.ListLinks(Empty())

    endpoint_ids = []
    links = []
        for link_ in grpc_links.links:
            if link_.link_id.link_uuid.uuid not in topo_link_uuids: continue
            links.append(link_)
            endpoint_ids.extend(link_.link_endpoint_ids)

        device_names, endpoints_data = get_endpoint_names(context_client, endpoint_ids)
    context_client.close()

@@ -54,10 +55,13 @@ def home():

@link.route('detail/<path:link_uuid>', methods=('GET', 'POST'))
def detail(link_uuid: str):
    request = LinkId()
    request.link_uuid.uuid = link_uuid  # pylint: disable=no-member
    context_client.connect()
    response = context_client.GetLink(request)
    device_names, endpoints_data = get_endpoint_names(context_client, response.link_endpoint_ids)
    link_obj = get_link(context_client, link_uuid, rw_copy=False)
    if link_obj is None:
        flash('Link({:s}) not found'.format(str(link_uuid)), 'danger')
        link_obj = Link()
        device_names, endpoints_data = dict(), dict()
    else:
        device_names, endpoints_data = get_endpoint_names(context_client, link_obj.link_endpoint_ids)
    context_client.close()
    return render_template('link/detail.html',link=response, device_names=device_names, endpoints_data=endpoints_data)
    return render_template('link/detail.html',link=link_obj, device_names=device_names, endpoints_data=endpoints_data)
+51 −47
Original line number Diff line number Diff line
@@ -14,8 +14,11 @@

import grpc
from flask import current_app, redirect, render_template, Blueprint, flash, session, url_for
from common.proto.context_pb2 import ContextId, Service, ServiceId, ServiceTypeEnum, ServiceStatusEnum, Connection
from common.proto.context_pb2 import (
    IsolationLevelEnum, Service, ServiceId, ServiceTypeEnum, ServiceStatusEnum, Connection)
from common.tools.context_queries.Context import get_context
from common.tools.context_queries.EndPoint import get_endpoint_names
from common.tools.context_queries.Service import get_service
from context.client.ContextClient import ContextClient
from service.client.ServiceClient import ServiceClient

@@ -26,93 +29,94 @@ service_client = ServiceClient()

@service.get('/')
def home():
    # flash('This is an info message', 'info')
    # flash('This is a danger message', 'danger')

    context_uuid = session.get('context_uuid', '-')
    if context_uuid == "-":
    if 'context_uuid' not in session or 'topology_uuid' not in session:
        flash("Please select a context!", "warning")
        return redirect(url_for("main.home"))
    request = ContextId()
    request.context_uuid.uuid = context_uuid
    context_uuid = session['context_uuid']

    context_client.connect()

    context_obj = get_context(context_client, context_uuid, rw_copy=False)
    if context_obj is None:
        flash('Context({:s}) not found'.format(str(context_uuid)), 'danger')
        services, device_names, endpoints_data = list(), list(), list()
    else:
        try:
        service_list = context_client.ListServices(request)
        # print(service_list)
        services = service_list.services
        context_found = True
            services = context_client.ListServices(context_obj.context_id)
            services = services.services
        except grpc.RpcError as e:
            if e.code() != grpc.StatusCode.NOT_FOUND: raise
            if e.details() != 'Context({:s}) not found'.format(context_uuid): raise
        services = []
        context_found = False

    if context_found:
        endpoint_ids = []
            services, device_names, endpoints_data = list(), dict(), dict()
        else:
            endpoint_ids = list()
            for service_ in services:
                endpoint_ids.extend(service_.service_endpoint_ids)
            device_names, endpoints_data = get_endpoint_names(context_client, endpoint_ids)
    else:
        device_names, endpoints_data = [],[]

    context_client.close()
    return render_template(
        'service/home.html', services=services, device_names=device_names, endpoints_data=endpoints_data,
        context_not_found=not context_found, ste=ServiceTypeEnum, sse=ServiceStatusEnum)
        ste=ServiceTypeEnum, sse=ServiceStatusEnum)


@service.route('add', methods=['GET', 'POST'])
def add():
    flash('Add service route called', 'danger')
    raise NotImplementedError()
    return render_template('service/home.html')
    #return render_template('service/home.html')


@service.get('<path:service_uuid>/detail')
def detail(service_uuid: str):
    context_uuid = session.get('context_uuid', '-')
    if context_uuid == "-":
    if 'context_uuid' not in session or 'topology_uuid' not in session:
        flash("Please select a context!", "warning")
        return redirect(url_for("main.home"))
    context_uuid = session['context_uuid']

    request: ServiceId = ServiceId()
    request.service_uuid.uuid = service_uuid
    request.context_id.context_uuid.uuid = context_uuid
    try:
        context_client.connect()
        response: Service = context_client.GetService(request)
        connections: Connection = context_client.ListConnections(request)

        endpoint_ids = list()
        service_obj = get_service(context_client, service_uuid, rw_copy=False)
        if service_obj is None:
            flash('Context({:s})/Service({:s}) not found'.format(str(context_uuid), str(service_uuid)), 'danger')
            service_obj = Service()
        else:
            endpoint_ids.extend(service_obj.service_endpoint_ids)
            connections: Connection = context_client.ListConnections(service_obj.service_id)
            connections = connections.connections
            for connection in connections: endpoint_ids.extend(connection.path_hops_endpoint_ids)

        endpoint_ids = []
        endpoint_ids.extend(response.service_endpoint_ids)
        for connection in connections:
            endpoint_ids.extend(connection.path_hops_endpoint_ids)
        if len(endpoint_ids) > 0:
            device_names, endpoints_data = get_endpoint_names(context_client, endpoint_ids)
        else:
            device_names, endpoints_data = dict(), dict()

        context_client.close()

        return render_template(
            'service/detail.html', service=service_obj, connections=connections, device_names=device_names,
            endpoints_data=endpoints_data, ste=ServiceTypeEnum, sse=ServiceStatusEnum, ile=IsolationLevelEnum)
    except Exception as e:
        flash('The system encountered an error and cannot show the details of this service.', 'warning')
        current_app.logger.exception(e)
        return redirect(url_for('service.home'))
    return render_template(
        'service/detail.html', service=response, connections=connections, device_names=device_names,
        endpoints_data=endpoints_data, ste=ServiceTypeEnum, sse=ServiceStatusEnum)


@service.get('<path:service_uuid>/delete')
def delete(service_uuid: str):
    context_uuid = session.get('context_uuid', '-')
    if context_uuid == "-":
    if 'context_uuid' not in session or 'topology_uuid' not in session:
        flash("Please select a context!", "warning")
        return redirect(url_for("main.home"))
    context_uuid = session['context_uuid']

    try:
        request = ServiceId()
        request.service_uuid.uuid = service_uuid
        request.context_id.context_uuid.uuid = context_uuid
        service_client.connect()
        response = service_client.DeleteService(request)
        service_client.DeleteService(request)
        service_client.close()

        flash('Service "{:s}" deleted successfully!'.format(service_uuid), 'success')
Loading