diff --git a/src/device/service/drivers/openconfig/OpenConfigDriver.py b/src/device/service/drivers/openconfig/OpenConfigDriver.py index 8c6e07b3f00a975a909161006e59e89de0ceaaf3..99ae1c8db2978e9a9fa3341eeed911f72b41130d 100644 --- a/src/device/service/drivers/openconfig/OpenConfigDriver.py +++ b/src/device/service/drivers/openconfig/OpenConfigDriver.py @@ -226,8 +226,11 @@ def edit_config( chk_length(str_resource_name, resource, min_length=2, max_length=2) resource_key,resource_value = resource chk_string(str_resource_name + '.key', resource_key, allow_empty=False) + str_config_messages = compose_config( # get template for configuration - resource_key, resource_value, delete=delete, vendor=netconf_handler.vendor, message_renderer=netconf_handler.message_renderer) + resource_key, resource_value, delete=delete, vendor=netconf_handler.vendor, message_renderer='pyangbind') + # str_config_messages = compose_config( # get template for configuration + # resource_key, resource_value, delete=delete, vendor=netconf_handler.vendor, message_renderer=netconf_handler.message_renderer) for str_config_message in str_config_messages: # configuration of the received templates if str_config_message is None: raise UnsupportedResourceKeyException(resource_key) logger.debug('[{:s}] str_config_message[{:d}] = {:s}'.format( diff --git a/src/device/service/drivers/openconfig/templates/Tools.py b/src/device/service/drivers/openconfig/templates/Tools.py index 79bebef5179b3464c33ce7fa0663b0cd35a51fc0..3e804368085537526728168f1edc09df1c57c1f9 100644 --- a/src/device/service/drivers/openconfig/templates/Tools.py +++ b/src/device/service/drivers/openconfig/templates/Tools.py @@ -61,7 +61,8 @@ def generate_templates(resource_key: str, resource_value: str, delete: bool,vend elif "inter_instance_policies" in resource_key: result_templates.append(associate_RP_to_NI(data)) elif "protocols" in resource_key: - if vendor == "ADVA": result_templates.append(add_protocol_NI(data, vendor, delete)) + result_templates.append(add_protocol_NI(data, vendor, delete)) + # if vendor == "ADVA": result_templates.append(add_protocol_NI(data, vendor, delete)) elif "table_connections" in resource_key: result_templates.append(create_table_conns(data, delete)) elif "interface" in resource_key: diff --git a/src/device/service/drivers/openconfig/templates/VPN/Network_instance_multivendor.py b/src/device/service/drivers/openconfig/templates/VPN/Network_instance_multivendor.py index c4d494ea61a307fbb5a53780f4ab37af2e7091a4..e36955a0d95e66f481289b7412ddaa327374e4ab 100644 --- a/src/device/service/drivers/openconfig/templates/VPN/Network_instance_multivendor.py +++ b/src/device/service/drivers/openconfig/templates/VPN/Network_instance_multivendor.py @@ -116,6 +116,9 @@ def add_protocol_NI(parameters,vendor, DEL): else: with tag('network-instance'): with tag('name'):text(parameters['name']) + with tag('config'): + with tag('name'): text(parameters['name']) + with tag('type', 'xmlns:oc-ni-types="http://openconfig.net/yang/network-instance-types"'): text('oc-ni-types:DEFAULT_INSTANCE') with tag('protocols'): with tag('protocol'): with tag('identifier', 'xmlns:oc-pol-types="http://openconfig.net/yang/policy-types"'):text('oc-pol-types:',parameters['identifier']) @@ -123,14 +126,41 @@ def add_protocol_NI(parameters,vendor, DEL): with tag('config'): with tag('identifier', 'xmlns:oc-pol-types="http://openconfig.net/yang/policy-types"'):text('oc-pol-types:',parameters['identifier']) with tag('name') :text(parameters['protocol_name']) + with tag('enabled'): text('true') if "BGP" in parameters['identifier']: with tag('bgp'): with tag('global'): + with tag('afi-safis'): + with tag('afi-safi'): + with tag('afi-safi-name', 'xmlns:oc-bgp-types="http://openconfig.net/yang/bgp-types"'): text('oc-bgp-types:IPV4_UNICAST') + with tag('config'): + with tag('afi-safi-name', 'xmlns:oc-bgp-types="http://openconfig.net/yang/bgp-types"'): text('oc-bgp-types:IPV4_UNICAST') + with tag('enabled'): text('true') with tag('config'): with tag('as') :text(parameters['as']) - if "router-id" in parameters: - with tag('router-id'):text(parameters['router-id']) - if vendor == "ADVA": + with tag('peer-groups'): + with tag('peer-group'): + with tag('peer-group-name'): text('IBGP') + with tag('config'): + with tag('peer-group-name'): text('IBGP') + with tag('peer-as'): text(parameters['protocol_name']) + with tag('afi-safis'): + with tag('afi-safi'): + with tag('afi-safi-name', 'xmlns:oc-bgp-types="http://openconfig.net/yang/bgp-types"'): text('oc-bgp-types:IPV4_UNICAST') + with tag('config'): + with tag('afi-safi-name', 'xmlns:oc-bgp-types="http://openconfig.net/yang/bgp-types"'): text('oc-bgp-types:IPV4_UNICAST') + with tag('enabled'): text('true') + + if 'neighbors' in parameters: + with tag('neighbors'): + for neighbor in parameters['neighbors']: + with tag('neighbor'): + with tag('neighbor-address'): text(neighbor['ip_address']) + with tag('config'): + with tag('neighbor-address'): text(neighbor['ip_address']) + with tag('peer-group'): text('IBGP') + # if vendor == "ADVA": + if True: with tag('tables'): with tag('table'): with tag('protocol', 'xmlns:oc-pol-types="http://openconfig.net/yang/policy-types"'):text('oc-pol-types:',parameters['identifier']) diff --git a/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Resources.py b/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Resources.py index 3fccbbb55c8959ae0c478526d2ea24882ebfef1f..394b50de8abd5a3ee7d8b8c3ffceafbb0546c2b0 100644 --- a/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Resources.py +++ b/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Resources.py @@ -13,7 +13,9 @@ # limitations under the License. import copy, deepmerge, json, logging +from typing import Dict from common.Constants import DEFAULT_CONTEXT_NAME +from werkzeug.exceptions import UnsupportedMediaType from context.client.ContextClient import ContextClient from flask_restful import Resource, request from service.client.ServiceClient import ServiceClient @@ -37,15 +39,20 @@ class BwInfo(_Resource): return bw_allocations def post(self): - bwinfo = request.get_json() - service = bwInfo_2_service(self.client, bwinfo) + if not request.is_json: + raise UnsupportedMediaType('JSON payload is required') + request_data: Dict = request.get_json() + service = bwInfo_2_service(self.client, request_data) stripped_service = copy.deepcopy(service) stripped_service.ClearField('service_endpoint_ids') stripped_service.ClearField('service_constraints') stripped_service.ClearField('service_config') - response = format_grpc_to_json(self.service_client.CreateService(stripped_service)) - response = format_grpc_to_json(self.service_client.UpdateService(service)) + try: + response = format_grpc_to_json(self.service_client.CreateService(stripped_service)) + response = format_grpc_to_json(self.service_client.UpdateService(service)) + except Exception as e: # pylint: disable=broad-except + return e return response diff --git a/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Tools.py b/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Tools.py index a78d2819317623b9ccaaec62e808f6435c88b630..d3be769c93614cfd46a08fbd238743d90f24ca85 100644 --- a/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Tools.py +++ b/src/nbi/service/rest_server/nbi_plugins/etsi_bwm/Tools.py @@ -14,22 +14,35 @@ import json import logging +import re import time from decimal import ROUND_HALF_EVEN, Decimal from flask.json import jsonify from common.proto.context_pb2 import ( - ContextId, Empty, EndPointId, ServiceId, ServiceTypeEnum, Service, Constraint, Constraint_SLA_Capacity, + ContextId, Empty, EndPointId, ServiceId, ServiceTypeEnum, Service, ServiceStatusEnum, Constraint, Constraint_SLA_Capacity, ConfigRule, ConfigRule_Custom, ConfigActionEnum) from common.tools.grpc.Tools import grpc_message_to_json +from common.tools.grpc.ConfigRules import update_config_rule_custom from common.tools.object_factory.Context import json_context_id from common.tools.object_factory.Service import json_service_id LOGGER = logging.getLogger(__name__) +ENDPOINT_SETTINGS_KEY = '/device[{:s}]/endpoint[{:s}]/vlan[{:d}]/settings' +RE_CONFIG_RULE_IF_SUBIF = re.compile(r'^\/interface\[([^\]]+)\]\/subinterface\[([^\]]+)\]$') +MEC_CONSIDERED_FIELDS = ['requestType', 'sessionFilter', 'fixedAllocation', 'allocationDirection'] +ALLOCATION_DIRECTION_DESCRIPTIONS = { + '00' : 'Downlink (towards the UE)', + '01' : 'Uplink (towards the application/session)', + '10' : 'Symmetrical'} +VLAN_TAG = 0 +PREFIX_LENGTH = 24 +BGP_AS = 65000 +policy_AZ = 'srv_{:d}_a'.format(VLAN_TAG) +policy_ZA = 'srv_{:d}_b'.format(VLAN_TAG) def service_2_bwInfo(service: Service) -> dict: response = {} - # allocationDirection = '??' # String: 00 = Downlink (towards the UE); 01 = Uplink (towards the application/session); 10 = Symmetrical response['appInsId'] = service.service_id.service_uuid.uuid # String: Application instance identifier for constraint in service.service_constraints: if constraint.WhichOneof('constraint') == 'sla_capacity': @@ -55,47 +68,108 @@ def service_2_bwInfo(service: Service) -> dict: return response -def bwInfo_2_service(client, bwInfo: dict) -> Service: +def bwInfo_2_service(client, bw_info: dict) -> Service: + # add description to allocationDirection code + if ad_code := bw_info.get('allocationDirection'): + bw_info['allocationDirection'] = {'code': ad_code, 'description': ALLOCATION_DIRECTION_DESCRIPTIONS[ad_code]} + if 'sessionFilter' in bw_info: + bw_info['sessionFilter'] = bw_info['sessionFilter'][0] # Discard other items in sessionFilter field + service = Service() - for key in ['allocationDirection', 'fixedBWPriority', 'requestType', 'timeStamp', 'sessionFilter']: - if key not in bwInfo: - continue - config_rule = ConfigRule() - config_rule.action = ConfigActionEnum.CONFIGACTION_SET - config_rule_custom = ConfigRule_Custom() - config_rule_custom.resource_key = key - if key != 'sessionFilter': - config_rule_custom.resource_value = str(bwInfo[key]) - else: - config_rule_custom.resource_value = json.dumps(bwInfo[key]) - config_rule.custom.CopyFrom(config_rule_custom) - service.service_config.config_rules.append(config_rule) - - if 'sessionFilter' in bwInfo: - a_ip = bwInfo['sessionFilter'][0]['sourceIp'] - z_ip = bwInfo['sessionFilter'][0]['dstAddress'] + + service_config_rules = service.service_config.config_rules + + route_distinguisher = '{:5d}:{:03d}'.format(BGP_AS, VLAN_TAG) + settings_cr_key = '/settings' + settings_cr_value = {'bgp_as':(BGP_AS, True), 'route_distinguisher': (route_distinguisher, True)} + update_config_rule_custom(service_config_rules, settings_cr_key, settings_cr_value) + + request_cr_key = '/request' + request_cr_value = {k:bw_info[k] for k in MEC_CONSIDERED_FIELDS} + + config_rule = ConfigRule() + config_rule.action = ConfigActionEnum.CONFIGACTION_SET + config_rule_custom = ConfigRule_Custom() + config_rule_custom.resource_key = request_cr_key + config_rule_custom.resource_value = json.dumps(request_cr_value) + config_rule.custom.CopyFrom(config_rule_custom) + service_config_rules.append(config_rule) + + if 'sessionFilter' in bw_info: + a_ip = bw_info['sessionFilter']['sourceIp'] + z_ip = bw_info['sessionFilter']['dstAddress'] devices = client.ListDevices(Empty()).devices + router_id_counter = 1 for device in devices: + device_endpoint_uuids = {ep.name:ep.endpoint_id.endpoint_uuid.uuid for ep in device.device_endpoints} for cr in device.device_config.config_rules: - if cr.WhichOneof('config_rule') == 'custom' and cr.custom.resource_key == '_connect/settings': - for ep in json.loads(cr.custom.resource_value)['endpoints']: - if 'ip' in ep and (ep['ip'] == a_ip or ep['ip'] == z_ip): - ep_id = EndPointId() - ep_id.endpoint_uuid.uuid = ep['uuid'] - ep_id.device_id.device_uuid.uuid = device.device_id.device_uuid.uuid - service.service_endpoint_ids.append(ep_id) - + if cr.WhichOneof('config_rule') != 'custom': + continue + match_subif = RE_CONFIG_RULE_IF_SUBIF.match(cr.custom.resource_key) + if not match_subif: + continue + address_ip = json.loads(cr.custom.resource_value).get('address_ip') + if address_ip not in [a_ip, z_ip]: + continue + port_name = 'PORT-' + match_subif.groups(0)[0] # `PORT-` added as prefix + ep_id = EndPointId() + ep_id.endpoint_uuid.uuid = device_endpoint_uuids[port_name] + ep_id.device_id.device_uuid.uuid = device.device_id.device_uuid.uuid + service.service_endpoint_ids.append(ep_id) + + # add interface config rules + endpoint_settings_key = ENDPOINT_SETTINGS_KEY.format(device.name, port_name, VLAN_TAG) + if address_ip == a_ip: + field_updates = { + 'address_ip': (address_ip, True), + # 'router_id': ('.'.join([str(router_id_counter)]*4), True), + 'router_id': ('200.1.1.1', True), + 'neighbor_address_ip': ('192.168.150.2', True), + 'route_distinguisher': (route_distinguisher, True), + 'sub_interface_index': (0, True), + 'vlan_id' : (VLAN_TAG, True), + # 'bgp_as': (BGP_AS+router_id_counter, True), + 'bgp_as': (BGP_AS, True), + 'ip_address': (address_ip, True), + 'prefix_length': (PREFIX_LENGTH, True), + 'policy_AZ' : (policy_AZ, True), + 'policy_ZA' : (policy_ZA, True), + 'address_prefix' : (PREFIX_LENGTH, True), + } + elif address_ip == z_ip: + field_updates = { + 'address_ip': (address_ip, True), + # 'router_id': ('.'.join([str(router_id_counter)]*4), True), + 'router_id': ('200.1.1.2', True), + 'neighbor_address_ip': ('192.168.150.1', True), + 'route_distinguisher': (route_distinguisher, True), + 'sub_interface_index': (0, True), + 'vlan_id' : (VLAN_TAG, True), + # 'bgp_as': (BGP_AS+router_id_counter, True), + 'bgp_as': (BGP_AS, True), + 'ip_address': (address_ip, True), + 'prefix_length': (PREFIX_LENGTH, True), + 'policy_AZ' : (policy_ZA, True), + 'policy_ZA' : (policy_AZ, True), + 'address_prefix' : (PREFIX_LENGTH, True), + } + router_id_counter += 1 + LOGGER.debug(f'BEFORE UPDATE -> device.device_config.config_rules: {service_config_rules}') + update_config_rule_custom(service_config_rules, endpoint_settings_key, field_updates) + LOGGER.debug(f'AFTER UPDATE -> device.device_config.config_rules: {service_config_rules}') + + service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_PLANNED service.service_type = ServiceTypeEnum.SERVICETYPE_L3NM - if 'appInsId' in bwInfo: - service.service_id.service_uuid.uuid = bwInfo['appInsId'] + if 'appInsId' in bw_info: + service.service_id.service_uuid.uuid = bw_info['appInsId'] service.service_id.context_id.context_uuid.uuid = 'admin' - service.name = bwInfo['appInsId'] + service.name = bw_info['appInsId'] - if 'fixedAllocation' in bwInfo: + if 'fixedAllocation' in bw_info: capacity = Constraint_SLA_Capacity() - capacity.capacity_gbps = float(bwInfo['fixedAllocation']) / 1.e9 + capacity.capacity_gbps = float(bw_info['fixedAllocation']) / 1.e9 constraint = Constraint() constraint.sla_capacity.CopyFrom(capacity) service.service_constraints.append(constraint) diff --git a/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py b/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py index 329552a914e478f0e927bd6f04fce6725bef0b5e..498db7dcd34b7f011f592cc16cb9ed0f6a14899f 100644 --- a/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py +++ b/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py @@ -24,6 +24,7 @@ SETTINGS_RULE_NAME = '/settings' DEVICE_SETTINGS = re.compile(r'\/device\[([^\]]+)\]\/settings') ENDPOINT_SETTINGS = re.compile(r'\/device\[([^\]]+)\]\/endpoint\[([^\]]+)\]\/settings') +RE_ENDPOINT_VLAN_SETTINGS = re.compile(r'\/device\[([^\]]+)\]\/endpoint\[([^\]]+)\]\/vlan\[([^\]]+)\]\/settings') L2NM_SETTINGS_FIELD_DEFAULTS = { 'encapsulation_type': 'dot1q', @@ -150,6 +151,23 @@ def compose_device_config_rules( device_endpoint_keys = set(itertools.product(device_keys, endpoint_keys)) if len(device_endpoint_keys.intersection(endpoints_traversed)) == 0: continue subservice_config_rules.append(config_rule) + + match = RE_ENDPOINT_VLAN_SETTINGS.match(config_rule.custom.resource_key) + if match is not None: + device_uuid_or_name = match.group(1) + device_name_or_uuid = device_name_mapping[device_uuid_or_name] + device_keys = {device_uuid_or_name, device_name_or_uuid} + + endpoint_uuid_or_name = match.group(2) + endpoint_name_or_uuid_1 = endpoint_name_mapping[(device_uuid_or_name, endpoint_uuid_or_name)] + endpoint_name_or_uuid_2 = endpoint_name_mapping[(device_name_or_uuid, endpoint_uuid_or_name)] + endpoint_keys = {endpoint_uuid_or_name, endpoint_name_or_uuid_1, endpoint_name_or_uuid_2} + + device_endpoint_keys = set(itertools.product(device_keys, endpoint_keys)) + if len(device_endpoint_keys.intersection(endpoints_traversed)) == 0: continue + # ! check later: vlan removed from config_rule + config_rule.custom.resource_key = re.sub('\/vlan\[[^\]]+\]', '', config_rule.custom.resource_key) + subservice_config_rules.append(config_rule) else: continue diff --git a/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py b/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py index 1e4425cdbcaa6ac1f423f2c3c65889e0e8017789..0369d6207ff659f381db76ac0742579a9eb7703f 100644 --- a/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py +++ b/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py @@ -37,6 +37,7 @@ def setup_config_rules( vlan_id = json_endpoint_settings.get('vlan_id', 1 ) # 400 address_ip = json_endpoint_settings.get('address_ip', '0.0.0.0') # '2.2.2.1' address_prefix = json_endpoint_settings.get('address_prefix', 24 ) # 30 + neighbor_address_ip = json_endpoint_settings.get('neighbor_address_ip', '0.0.0.0') # '2.2.2.1' policy_import = json_endpoint_settings.get('policy_AZ', '2' ) # 2 policy_export = json_endpoint_settings.get('policy_ZA', '7' ) # 30 @@ -46,18 +47,21 @@ def setup_config_rules( network_subinterface_desc = json_endpoint_settings.get('subif_description','') #service_short_uuid = service_uuid.split('-')[-1] #network_instance_name = '{:s}-NetInst'.format(service_short_uuid) - network_instance_name = json_endpoint_settings.get('ni_name', service_uuid.split('-')[-1]) #ELAN-AC:1 + # network_instance_name = json_endpoint_settings.get('ni_name', service_uuid.split('-')[-1]) #ELAN-AC:1 + network_instance_name = 'default' - if_subif_name = '{:s}.{:d}'.format(endpoint_name, vlan_id) + ''' + # if_subif_name = '{:s}.{:d}'.format(endpoint_name, vlan_id) + if_subif_name = '{:s}'.format(endpoint_name[5:]) json_config_rules = [ - # Configure Interface (not used) - #json_config_rule_set( + # # Configure Interface (not used) + # json_config_rule_set( # '/interface[{:s}]'.format(endpoint_name), { # 'name': endpoint_name, # 'description': network_interface_desc, # 'mtu': mtu, - #}), + # }), #Create network instance json_config_rule_set( @@ -74,8 +78,10 @@ def setup_config_rules( json_config_rule_set( '/network_instance[{:s}]/protocols[BGP]'.format(network_instance_name), { 'name': network_instance_name, - 'protocol_name': 'BGP', + # 'protocol_name': 'BGP', + 'protocol_name': bgp_as, 'identifier': 'BGP', + # 'identifier': bgp_as, 'as': bgp_as, 'router_id': router_id, }), @@ -101,7 +107,8 @@ def setup_config_rules( json_config_rule_set( '/interface[{:s}]/subinterface[{:d}]'.format(if_subif_name, sub_interface_index), { 'name' : if_subif_name, - 'type' :'l3ipvlan', + # 'type' :'l3ipvlan', + 'type' :'ethernetCsmacd', 'mtu' : mtu, 'index' : sub_interface_index, 'description' : network_subinterface_desc, @@ -183,6 +190,40 @@ def setup_config_rules( }), ] + ''' + if_subif_name = '{:s}'.format(endpoint_name[5:]) + + json_config_rules = [ + + #Add DIRECTLY CONNECTED protocol to network instance + json_config_rule_set( + '/network_instance[{:s}]/protocols[DIRECTLY_CONNECTED]'.format(network_instance_name), { + 'name': network_instance_name, + 'identifier': 'DIRECTLY_CONNECTED', + 'protocol_name': 'DIRECTLY_CONNECTED', + }), + + # Add BGP neighbors + json_config_rule_set( + '/network_instance[{:s}]/protocols[BGP]'.format(network_instance_name), { + 'name': network_instance_name, + 'protocol_name': bgp_as, + 'identifier': 'BGP', + 'as': bgp_as, + 'router_id': router_id, + 'neighbors': [{'ip_address': neighbor_address_ip, 'remote_as': bgp_as}] + }), + json_config_rule_set( + '/network_instance[{:s}]/table_connections[DIRECTLY_CONNECTED][BGP][IPV4]'.format(network_instance_name), { + 'name' : network_instance_name, + 'src_protocol' : 'DIRECTLY_CONNECTED', + 'dst_protocol' : 'BGP', + 'address_family' : 'IPV4', + 'default_import_policy': 'ACCEPT_ROUTE', + }), + + ] + for res_key, res_value in endpoint_acls: json_config_rules.append( {'action': 1, 'acl': res_value} @@ -201,7 +242,8 @@ def teardown_config_rules( json_endpoint_settings : Dict = endpoint_settings.value service_short_uuid = service_uuid.split('-')[-1] - network_instance_name = '{:s}-NetInst'.format(service_short_uuid) + # network_instance_name = '{:s}-NetInst'.format(service_short_uuid) + network_instance_name = json_endpoint_settings.get('ni_name', service_short_uuid) #ELAN-AC:1 #network_interface_desc = '{:s}-NetIf'.format(service_uuid) #network_subinterface_desc = '{:s}-NetSubIf'.format(service_uuid) @@ -262,10 +304,10 @@ def teardown_config_rules( #Delete interface; automatically deletes: # - /interface[]/subinterface[] - json_config_rule_delete('/interface[{:s}]/subinterface[0]'.format(if_subif_name), - { - 'name': if_subif_name, - }), + # json_config_rule_delete('/interface[{:s}]/subinterface[0]'.format(if_subif_name), + # { + # 'name': if_subif_name, + # }), #Delete network instance; automatically deletes: # - /network_instance[]/interface[]