Merge "Add IxNextgen API for checking protocols status"
[yardstick.git] / yardstick / network_services / libs / ixia_libs / ixnet / ixnet_api.py
index a840fea..017b4b6 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2016-2017 Intel Corporation
+# Copyright (c) 2016-2018 Intel Corporation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -34,12 +34,35 @@ PROTO_UDP = 'udp'
 PROTO_TCP = 'tcp'
 PROTO_VLAN = 'vlan'
 
-IP_VERSION_4_MASK = 24
-IP_VERSION_6_MASK = 64
+SINGLE_VALUE = "singleValue"
+
+S_VLAN = 0
+C_VLAN = 1
+
+ETHER_TYPE_802_1ad = '0x88a8'
 
 TRAFFIC_STATUS_STARTED = 'started'
 TRAFFIC_STATUS_STOPPED = 'stopped'
 
+PROTOCOL_STATUS_UP = 'up'
+PROTOCOL_STATUS_DOWN = ['down', 'notStarted']
+
+SUPPORTED_PROTO = [PROTO_UDP]
+
+
+class Vlan(object):
+    def __init__(self,
+                 vlan_id, vlan_id_step=None, vlan_id_direction='increment',
+                 prio=None, prio_step=None, prio_direction='increment',
+                 tp_id=None):
+        self.vlan_id = vlan_id
+        self.vlan_id_step = vlan_id_step
+        self.vlan_id_direction = vlan_id_direction
+        self.prio = prio
+        self.prio_step = prio_step
+        self.prio_direction = prio_direction
+        self.tp_id = tp_id
+
 
 # NOTE(ralonsoh): this pragma will be removed in the last patch of this series
 class IxNextgen(object):  # pragma: no cover
@@ -98,6 +121,11 @@ class IxNextgen(object):  # pragma: no cover
             return self._ixnet
         raise exceptions.IxNetworkClientNotConnected()
 
+    def get_vports(self):
+        """Return the list of assigned ports (vport objects)"""
+        vports = self.ixnet.getList(self.ixnet.getRoot(), 'vport')
+        return vports
+
     def _get_config_element_by_flow_group_name(self, flow_group_name):
         """Get a config element using the flow group name
 
@@ -152,6 +180,15 @@ class IxNextgen(object):  # pragma: no cover
         return self.ixnet.getAttribute(self.ixnet.getRoot() + 'traffic',
                                        '-state')
 
+    def _get_protocol_status(self, proto):
+        """Get protocol status
+
+        :param proto: IxNet protocol str representation, e.g.:
+        '::ixNet::OBJ-/topology:2/deviceGroup:1/ethernet:1/ipv4:L14'
+        :return: (str) protocol status: 'up', 'down' or 'notStarted'
+        """
+        return self.ixnet.getAttribute(proto, '-sessionStatus')[0]
+
     def is_traffic_running(self):
         """Returns true if traffic state == TRAFFIC_STATUS_STARTED"""
         return self._get_traffic_state() == TRAFFIC_STATUS_STARTED
@@ -160,6 +197,28 @@ class IxNextgen(object):  # pragma: no cover
         """Returns true if traffic state == TRAFFIC_STATUS_STOPPED"""
         return self._get_traffic_state() == TRAFFIC_STATUS_STOPPED
 
+    def is_protocols_running(self, protocols):
+        """Returns true if all protocols statuses are PROTOCOL_STATUS_UP
+
+        :param protocols: list of protocols str representations, e.g.:
+        ['::ixNet::OBJ-/topology:2/deviceGroup:1/ethernet:1/ipv4:L14', ...]
+        :return: (bool) True if all protocols status is 'up', False if any
+        protocol status is 'down' or 'notStarted'
+        """
+        return all(self._get_protocol_status(proto) == PROTOCOL_STATUS_UP
+                   for proto in protocols)
+
+    def is_protocols_stopped(self, protocols):
+        """Returns true if all protocols statuses are in PROTOCOL_STATUS_DOWN
+
+        :param protocols: list of protocols str representations, e.g.:
+        ['::ixNet::OBJ-/topology:2/deviceGroup:1/ethernet:1/ipv4:L14', ...]
+        :return: (bool) True if all protocols status is 'down' or 'notStarted',
+        False if any protocol status is 'up'
+        """
+        return all(self._get_protocol_status(proto) in PROTOCOL_STATUS_DOWN
+                   for proto in protocols)
+
     @staticmethod
     def _parse_framesize(framesize):
         """Parse "framesize" config param. to return a list of weighted pairs
@@ -216,15 +275,20 @@ class IxNextgen(object):  # pragma: no cover
                  zip(self._cfg['cards'], self._cfg['ports'])]
 
         log.info('Create and assign vports: %s', ports)
-        for port in ports:
-            vport = self.ixnet.add(self.ixnet.getRoot(), 'vport')
-            self.ixnet.commit()
-            self.ixnet.execute('assignPorts', [port], [], [vport], True)
+
+        vports = []
+        for _ in ports:
+            vports.append(self.ixnet.add(self.ixnet.getRoot(), 'vport'))
             self.ixnet.commit()
+
+        self.ixnet.execute('assignPorts', ports, [], vports, True)
+        self.ixnet.commit()
+
+        for vport in vports:
             if self.ixnet.getAttribute(vport, '-state') != 'up':
                 log.warning('Port %s is down', vport)
 
-    def _create_traffic_item(self):
+    def _create_traffic_item(self, traffic_type='raw'):
         """Create the traffic item to hold the flow groups
 
         The traffic item tracking by "Traffic Item" is enabled to retrieve the
@@ -234,7 +298,7 @@ class IxNextgen(object):  # pragma: no cover
         traffic_item = self.ixnet.add(self.ixnet.getRoot() + '/traffic',
                                       'trafficItem')
         self.ixnet.setMultiAttribute(traffic_item, '-name', 'RFC2544',
-                                     '-trafficType', 'raw')
+                                     '-trafficType', traffic_type)
         self.ixnet.commit()
 
         traffic_item_id = self.ixnet.remapIds(traffic_item)[0]
@@ -242,27 +306,25 @@ class IxNextgen(object):  # pragma: no cover
                                 '-trackBy', 'trafficGroupId0')
         self.ixnet.commit()
 
-    def _create_flow_groups(self):
-        """Create the flow groups between the assigned ports"""
+    def _create_flow_groups(self, uplink, downlink):
+        """Create the flow groups between the endpoints"""
         traffic_item_id = self.ixnet.getList(self.ixnet.getRoot() + 'traffic',
                                              'trafficItem')[0]
         log.info('Create the flow groups')
-        vports = self.ixnet.getList(self.ixnet.getRoot(), 'vport')
-        uplink_ports = vports[::2]
-        downlink_ports = vports[1::2]
+
         index = 0
-        for up, down in zip(uplink_ports, downlink_ports):
+        for up, down in zip(uplink, downlink):
             log.info('FGs: %s <--> %s', up, down)
             endpoint_set_1 = self.ixnet.add(traffic_item_id, 'endpointSet')
             endpoint_set_2 = self.ixnet.add(traffic_item_id, 'endpointSet')
             self.ixnet.setMultiAttribute(
                 endpoint_set_1, '-name', str(index + 1),
-                '-sources', [up + '/protocols'],
-                '-destinations', [down + '/protocols'])
+                '-sources', [up],
+                '-destinations', [down])
             self.ixnet.setMultiAttribute(
                 endpoint_set_2, '-name', str(index + 2),
-                '-sources', [down + '/protocols'],
-                '-destinations', [up + '/protocols'])
+                '-sources', [down],
+                '-destinations', [up])
             self.ixnet.commit()
             index += 2
 
@@ -272,7 +334,7 @@ class IxNextgen(object):  # pragma: no cover
                     '/traffic/protocolTemplate:"{}"'.format(protocol_name))
         self.ixnet.execute('append', previous_element, protocol)
 
-    def _setup_config_elements(self):
+    def _setup_config_elements(self, add_default_proto=True):
         """Setup the config elements
 
         The traffic item is configured to allow individual configurations per
@@ -294,12 +356,13 @@ class IxNextgen(object):  # pragma: no cover
             self.ixnet.setAttribute(config_element + '/frameRateDistribution',
                                     '-streamDistribution', 'splitRateEvenly')
             self.ixnet.commit()
-            self._append_procotol_to_stack(
-                PROTO_UDP, config_element + '/stack:"ethernet-1"')
-            self._append_procotol_to_stack(
-                PROTO_IPV4, config_element + '/stack:"ethernet-1"')
+            if add_default_proto:
+                self._append_procotol_to_stack(
+                    PROTO_UDP, config_element + '/stack:"ethernet-1"')
+                self._append_procotol_to_stack(
+                    PROTO_IPV4, config_element + '/stack:"ethernet-1"')
 
-    def create_traffic_model(self):
+    def create_traffic_model(self, uplink_ports, downlink_ports):
         """Create a traffic item and the needed flow groups
 
         Each flow group inside the traffic item (only one is present)
@@ -310,10 +373,27 @@ class IxNextgen(object):  # pragma: no cover
             FlowGroup3: port3    -> port4
             FlowGroup4: port3    <- port4
         """
-        self._create_traffic_item()
-        self._create_flow_groups()
+        self._create_traffic_item('raw')
+        uplink_endpoints = [port + '/protocols' for port in uplink_ports]
+        downlink_endpoints = [port + '/protocols' for port in downlink_ports]
+        self._create_flow_groups(uplink_endpoints, downlink_endpoints)
         self._setup_config_elements()
 
+    def create_ipv4_traffic_model(self, uplink_topologies, downlink_topologies):
+        """Create a traffic item and the needed flow groups
+
+        Each flow group inside the traffic item (only one is present)
+        represents the traffic between two topologies:
+                        (uplink)    (downlink)
+            FlowGroup1: uplink1    -> downlink1
+            FlowGroup2: uplink1    <- downlink1
+            FlowGroup3: uplink2    -> downlink2
+            FlowGroup4: uplink2    <- downlink2
+        """
+        self._create_traffic_item('ipv4')
+        self._create_flow_groups(uplink_topologies, downlink_topologies)
+        self._setup_config_elements(False)
+
     def _update_frame_mac(self, ethernet_descriptor, field, mac_address):
         """Set the MAC address in a config element stack Ethernet field
 
@@ -330,7 +410,7 @@ class IxNextgen(object):  # pragma: no cover
                                      '-valueType', 'singleValue')
         self.ixnet.commit()
 
-    def update_frame(self, traffic):
+    def update_frame(self, traffic, duration):
         """Update the L2 frame
 
         This function updates the L2 frame options:
@@ -348,6 +428,7 @@ class IxNextgen(object):  # pragma: no cover
 
         :param traffic: list of traffic elements; each traffic element contains
                         the injection parameter for each flow group.
+        :param duration: (int) injection time in seconds.
         """
         for traffic_param in traffic.values():
             fg_id = str(traffic_param['id'])
@@ -356,38 +437,81 @@ class IxNextgen(object):  # pragma: no cover
                 raise exceptions.IxNetworkFlowNotPresent(flow_group=fg_id)
 
             type = traffic_param.get('traffic_type', 'fixedDuration')
-            duration = traffic_param.get('duration', 30)
-            rate = traffic_param['rate']
             rate_unit = (
                 'framesPerSecond' if traffic_param['rate_unit'] ==
                 tp_base.TrafficProfileConfig.RATE_FPS else 'percentLineRate')
             weighted_range_pairs = self._parse_framesize(
-                traffic_param['outer_l2']['framesize'])
-            srcmac = str(traffic_param.get('srcmac', '00:00:00:00:00:01'))
-            dstmac = str(traffic_param.get('dstmac', '00:00:00:00:00:02'))
-            # NOTE(ralonsoh): add QinQ tagging when
-            # traffic_param['outer_l2']['QinQ'] exists.
-            # s_vlan = traffic_param['outer_l2']['QinQ']['S-VLAN']
-            # c_vlan = traffic_param['outer_l2']['QinQ']['C-VLAN']
+                traffic_param['outer_l2'].get('framesize', {}))
+            srcmac = str(traffic_param['outer_l2'].get('srcmac', '00:00:00:00:00:01'))
+            dstmac = str(traffic_param['outer_l2'].get('dstmac', '00:00:00:00:00:02'))
+
+            if traffic_param['outer_l2'].get('QinQ'):
+                s_vlan = traffic_param['outer_l2']['QinQ']['S-VLAN']
+                c_vlan = traffic_param['outer_l2']['QinQ']['C-VLAN']
+
+                field_descriptor = self._get_field_in_stack_item(
+                    self._get_stack_item(fg_id, PROTO_ETHERNET)[0],
+                    'etherType')
+
+                self.ixnet.setMultiAttribute(field_descriptor,
+                                             '-auto', 'false',
+                                             '-singleValue', ETHER_TYPE_802_1ad,
+                                             '-fieldValue', ETHER_TYPE_802_1ad,
+                                             '-valueType', SINGLE_VALUE)
+
+                self._append_procotol_to_stack(
+                    PROTO_VLAN, config_element + '/stack:"ethernet-1"')
+                self._append_procotol_to_stack(
+                    PROTO_VLAN, config_element + '/stack:"ethernet-1"')
+
+                self._update_vlan_tag(fg_id, s_vlan, S_VLAN)
+                self._update_vlan_tag(fg_id, c_vlan, C_VLAN)
 
             self.ixnet.setMultiAttribute(
                 config_element + '/transmissionControl',
                 '-type', type, '-duration', duration)
+
             self.ixnet.setMultiAttribute(
                 config_element + '/frameRate',
-                '-rate', rate, '-type', rate_unit)
-            self.ixnet.setMultiAttribute(
-                config_element + '/frameSize',
-                '-type', 'weightedPairs',
-                '-weightedRangePairs', weighted_range_pairs)
+                '-rate', traffic_param['rate'], '-type', rate_unit)
+
+            if len(weighted_range_pairs):
+                self.ixnet.setMultiAttribute(
+                    config_element + '/frameSize',
+                    '-type', 'weightedPairs',
+                    '-weightedRangePairs', weighted_range_pairs)
+
             self.ixnet.commit()
 
-            self._update_frame_mac(
-                self._get_stack_item(fg_id, PROTO_ETHERNET)[0],
-                'destinationAddress', dstmac)
-            self._update_frame_mac(
-                self._get_stack_item(fg_id, PROTO_ETHERNET)[0],
-                'sourceAddress', srcmac)
+            if dstmac:
+                self._update_frame_mac(
+                    self._get_stack_item(fg_id, PROTO_ETHERNET)[0],
+                    'destinationAddress', dstmac)
+            if srcmac:
+                self._update_frame_mac(
+                    self._get_stack_item(fg_id, PROTO_ETHERNET)[0],
+                    'sourceAddress', srcmac)
+
+    def _update_vlan_tag(self, fg_id, params, vlan=0):
+        field_to_param_map = {
+            'vlanUserPriority': 'priority',
+            'cfi': 'cfi',
+            'vlanID': 'id'
+        }
+        for field, param in field_to_param_map.items():
+            value = params.get(param)
+            if value:
+                field_descriptor = self._get_field_in_stack_item(
+                    self._get_stack_item(fg_id, PROTO_VLAN)[vlan],
+                    field)
+
+                self.ixnet.setMultiAttribute(field_descriptor,
+                                             '-auto', 'false',
+                                             '-singleValue', value,
+                                             '-fieldValue', value,
+                                             '-valueType', SINGLE_VALUE)
+
+        self.ixnet.commit()
 
     def _update_ipv4_address(self, ip_descriptor, field, ip_address, seed,
                              mask, count):
@@ -426,18 +550,89 @@ class IxNextgen(object):  # pragma: no cover
             if not self._get_config_element_by_flow_group_name(fg_id):
                 raise exceptions.IxNetworkFlowNotPresent(flow_group=fg_id)
 
-            count = traffic_param['outer_l3']['count']
-            srcip = str(traffic_param['outer_l3']['srcip'])
-            dstip = str(traffic_param['outer_l3']['dstip'])
-            srcmask = traffic_param['outer_l3']['srcmask'] or IP_VERSION_4_MASK
-            dstmask = traffic_param['outer_l3']['dstmask'] or IP_VERSION_4_MASK
+            if traffic_param['outer_l3']:
+                count = traffic_param['outer_l3']['count']
+                srcip = traffic_param['outer_l3']['srcip']
+                dstip = traffic_param['outer_l3']['dstip']
+                srcseed = traffic_param['outer_l3']['srcseed']
+                dstseed = traffic_param['outer_l3']['dstseed']
+                srcmask = traffic_param['outer_l3']['srcmask'] \
+                          or ipaddress.IPV4LENGTH
+                dstmask = traffic_param['outer_l3']['dstmask'] \
+                          or ipaddress.IPV4LENGTH
+                if srcip:
+                    self._update_ipv4_address(
+                        self._get_stack_item(fg_id, PROTO_IPV4)[0],
+                        'srcIp', str(srcip), srcseed, srcmask, count)
+                if dstip:
+                    self._update_ipv4_address(
+                        self._get_stack_item(fg_id, PROTO_IPV4)[0],
+                        'dstIp', str(dstip), dstseed, dstmask, count)
+
+    def update_l4(self, traffic):
+        """Update the L4 headers
+
+        NOTE: Only UDP is currently supported
+        :param traffic: list of traffic elements; each traffic element contains
+                        the injection parameter for each flow group
+        """
+        for traffic_param in traffic.values():
+            fg_id = str(traffic_param['id'])
+            if not self._get_config_element_by_flow_group_name(fg_id):
+                raise exceptions.IxNetworkFlowNotPresent(flow_group=fg_id)
+
+            proto = traffic_param['outer_l3'].get('proto')
+            if not (proto and traffic_param['outer_l4']):
+                continue
+
+            if proto not in SUPPORTED_PROTO:
+                raise exceptions.IXIAUnsupportedProtocol(protocol=proto)
+
+            count = traffic_param['outer_l4']['count']
+            seed = traffic_param['outer_l4']['seed']
+
+            srcport = traffic_param['outer_l4']['srcport']
+            srcmask = traffic_param['outer_l4']['srcportmask']
+
+            dstport = traffic_param['outer_l4']['dstport']
+            dstmask = traffic_param['outer_l4']['dstportmask']
+
+            if proto == PROTO_UDP:
+                if srcport:
+                    self._update_udp_port(
+                        self._get_stack_item(fg_id, proto)[0],
+                        'srcPort', srcport, seed, srcmask, count)
+                if dstport:
+                    self._update_udp_port(
+                        self._get_stack_item(fg_id, proto)[0],
+                        'dstPort', dstport, seed, dstmask, count)
+
+    def _update_udp_port(self, descriptor, field, value,
+                         seed=1, mask=0, count=1):
+        """Set the UDP port in a config element stack UDP field
+
+        :param udp_descriptor: (str) UDP descriptor, e.g.:
+            /traffic/trafficItem:1/configElement:1/stack:"udp-3"
+        :param field: (str) field name, e.g.: scrPort, dstPort
+        :param value: (int) UDP port fixed bits
+        :param seed: (int) seed length
+        :param mask: (int) UDP port mask
+        :param count: (int) number of random ports to generate
+        """
+        field_descriptor = self._get_field_in_stack_item(descriptor, field)
+
+        if mask == 0:
+            seed = count = 1
+
+        self.ixnet.setMultiAttribute(field_descriptor,
+                                     '-auto', 'false',
+                                     '-seed', seed,
+                                     '-fixedBits', value,
+                                     '-randomMask', mask,
+                                     '-valueType', 'random',
+                                     '-countValue', count)
 
-            self._update_ipv4_address(
-                self._get_stack_item(fg_id, PROTO_IPV4)[0],
-                'srcIp', srcip, 1, srcmask, count)
-            self._update_ipv4_address(
-                self._get_stack_item(fg_id, PROTO_IPV4)[0],
-                'dstIp', dstip, 1, dstmask, count)
+        self.ixnet.commit()
 
     def _build_stats_map(self, view_obj, name_map):
         return {data_yardstick: self.ixnet.execute(
@@ -461,6 +656,12 @@ class IxNextgen(object):  # pragma: no cover
                                           self.LATENCY_NAME_MAP))
         return stats
 
+    def start_protocols(self):
+        self.ixnet.execute('startAllProtocols')
+
+    def stop_protocols(self):
+        self.ixnet.execute('stopAllProtocols')
+
     def start_traffic(self):
         """Start the traffic injection in the traffic item
 
@@ -478,3 +679,194 @@ class IxNextgen(object):  # pragma: no cover
         self.ixnet.execute('start', '/traffic')
         # pylint: disable=unnecessary-lambda
         utils.wait_until_true(lambda: self.is_traffic_running())
+
+    def add_topology(self, name, vports):
+        log.debug("add_topology: name='%s' ports='%s'", name, vports)
+        obj = self.ixnet.add(self.ixnet.getRoot(), 'topology')
+        self.ixnet.setMultiAttribute(obj, '-name', name, '-vports', vports)
+        self.ixnet.commit()
+        return obj
+
+    def add_device_group(self, topology, name, multiplier):
+        log.debug("add_device_group: tpl='%s', name='%s', multiplier='%s'",
+                  topology, name, multiplier)
+
+        obj = self.ixnet.add(topology, 'deviceGroup')
+        self.ixnet.setMultiAttribute(obj, '-name', name, '-multiplier',
+                                     multiplier)
+        self.ixnet.commit()
+        return obj
+
+    def add_ethernet(self, dev_group, name):
+        log.debug(
+            "add_ethernet: device_group='%s' name='%s'", dev_group, name)
+        obj = self.ixnet.add(dev_group, 'ethernet')
+        self.ixnet.setMultiAttribute(obj, '-name', name)
+        self.ixnet.commit()
+        return obj
+
+    def _create_vlans(self, ethernet, count):
+        self.ixnet.setMultiAttribute(ethernet, '-useVlans', 'true')
+        self.ixnet.setMultiAttribute(ethernet, '-vlanCount', count)
+        self.ixnet.commit()
+
+    def _configure_vlans(self, ethernet, vlans):
+        vlans_obj = self.ixnet.getList(ethernet, 'vlan')
+        for i, vlan_obj in enumerate(vlans_obj):
+            if vlans[i].vlan_id_step is not None:
+                vlan_id_obj = self.ixnet.getAttribute(vlan_obj, '-vlanId')
+                self.ixnet.setMultiAttribute(vlan_id_obj, '-clearOverlays',
+                                             'true', '-pattern', 'counter')
+                vlan_id_counter = self.ixnet.add(vlan_id_obj, 'counter')
+                self.ixnet.setMultiAttribute(vlan_id_counter, '-start',
+                                             vlans[i].vlan_id, '-step',
+                                             vlans[i].vlan_id_step,
+                                             '-direction',
+                                             vlans[i].vlan_id_direction)
+            else:
+                vlan_id_obj = self.ixnet.getAttribute(vlan_obj, '-vlanId')
+                self.ixnet.setMultiAttribute(vlan_id_obj + '/singleValue',
+                                             '-value', vlans[i].vlan_id)
+
+            if vlans[i].prio_step is not None:
+                prio_obj = self.ixnet.getAttribute(vlan_obj, '-priority')
+                self.ixnet.setMultiAttribute(prio_obj, '-clearOverlays', 'true',
+                                             '-pattern', 'counter')
+                prio_counter = self.ixnet.add(prio_obj, 'counter')
+                self.ixnet.setMultiAttribute(prio_counter,
+                                        '-start', vlans[i].prio,
+                                        '-step', vlans[i].prio_step,
+                                        '-direction', vlans[i].prio_direction)
+            elif vlans[i].prio is not None:
+                prio_obj = self.ixnet.getAttribute(vlan_obj, '-priority')
+                self.ixnet.setMultiAttribute(prio_obj + '/singleValue',
+                                             '-value', vlans[i].prio)
+
+            if vlans[i].tp_id is not None:
+                tp_id_obj = self.ixnet.getAttribute(vlan_obj, '-tpid')
+                self.ixnet.setMultiAttribute(tp_id_obj + '/singleValue',
+                                             '-value', vlans[i].tp_id)
+
+        self.ixnet.commit()
+
+    def add_vlans(self, ethernet, vlans):
+        log.debug("add_vlans: ethernet='%s'", ethernet)
+
+        if vlans is None or len(vlans) == 0:
+            raise RuntimeError(
+                "Invalid 'vlans' argument. Expected list of Vlan instances.")
+
+        self._create_vlans(ethernet, len(vlans))
+        self._configure_vlans(ethernet, vlans)
+
+    def add_ipv4(self, ethernet, name='',
+                 addr=None, addr_step=None, addr_direction='increment',
+                 prefix=None, prefix_step=None, prefix_direction='increment',
+                 gateway=None, gw_step=None, gw_direction='increment'):
+        log.debug("add_ipv4: ethernet='%s' name='%s'", ethernet, name)
+        obj = self.ixnet.add(ethernet, 'ipv4')
+        if name != '':
+            self.ixnet.setAttribute(obj, '-name', name)
+            self.ixnet.commit()
+
+        if addr_step is not None:
+            # handle counter pattern
+            _address = self.ixnet.getAttribute(obj, '-address')
+            self.ixnet.setMultiAttribute(_address, '-clearOverlays', 'true',
+                                         '-pattern', 'counter')
+
+            address_counter = self.ixnet.add(_address, 'counter')
+            self.ixnet.setMultiAttribute(address_counter,
+                                         '-start', addr,
+                                         '-step', addr_step,
+                                         '-direction', addr_direction)
+        elif addr is not None:
+            # handle single value
+            _address = self.ixnet.getAttribute(obj, '-address')
+            self.ixnet.setMultiAttribute(_address + '/singleValue', '-value',
+                                         addr)
+
+        if prefix_step is not None:
+            # handle counter pattern
+            _prefix = self.ixnet.getAttribute(obj, '-prefix')
+            self.ixnet.setMultiAttribute(_prefix, '-clearOverlays', 'true',
+                                         '-pattern', 'counter')
+            prefix_counter = self.ixnet.add(_prefix, 'counter')
+            self.ixnet.setMultiAttribute(prefix_counter,
+                                         '-start', prefix,
+                                         '-step', prefix_step,
+                                         '-direction', prefix_direction)
+        elif prefix is not None:
+            # handle single value
+            _prefix = self.ixnet.getAttribute(obj, '-prefix')
+            self.ixnet.setMultiAttribute(_prefix + '/singleValue', '-value',
+                                         prefix)
+
+        if gw_step is not None:
+            # handle counter pattern
+            _gateway = self.ixnet.getAttribute(obj, '-gatewayIp')
+            self.ixnet.setMultiAttribute(_gateway, '-clearOverlays', 'true',
+                                         '-pattern', 'counter')
+
+            gateway_counter = self.ixnet.add(_gateway, 'counter')
+            self.ixnet.setMultiAttribute(gateway_counter,
+                                         '-start', gateway,
+                                         '-step', gw_step,
+                                         '-direction', gw_direction)
+        elif gateway is not None:
+            # handle single value
+            _gateway = self.ixnet.getAttribute(obj, '-gatewayIp')
+            self.ixnet.setMultiAttribute(_gateway + '/singleValue', '-value',
+                                         gateway)
+
+        self.ixnet.commit()
+        return obj
+
+    def add_pppox_client(self, xproto, auth, user, pwd):
+        log.debug(
+            "add_pppox_client: xproto='%s', auth='%s', user='%s', pwd='%s'",
+            xproto, auth, user, pwd)
+        obj = self.ixnet.add(xproto, 'pppoxclient')
+        self.ixnet.commit()
+
+        if auth == 'pap':
+            auth_type = self.ixnet.getAttribute(obj, '-authType')
+            self.ixnet.setMultiAttribute(auth_type + '/singleValue', '-value',
+                                         auth)
+            pap_user = self.ixnet.getAttribute(obj, '-papUser')
+            self.ixnet.setMultiAttribute(pap_user + '/singleValue', '-value',
+                                         user)
+            pap_pwd = self.ixnet.getAttribute(obj, '-papPassword')
+            self.ixnet.setMultiAttribute(pap_pwd + '/singleValue', '-value',
+                                         pwd)
+        else:
+            raise NotImplementedError()
+
+        self.ixnet.commit()
+        return obj
+
+    def add_bgp(self, ipv4, dut_ip, local_as, bgp_type=None):
+        """Add BGP protocol"""
+        log.debug("add_bgp: ipv4='%s', dut_ip='%s', local_as='%s'", ipv4,
+                  dut_ip, local_as)
+        obj = self.ixnet.add(ipv4, 'bgpIpv4Peer')
+        self.ixnet.commit()
+
+        # Set DUT IP address
+        dut_ip_addr = self.ixnet.getAttribute(obj, '-dutIp')
+        self.ixnet.setAttribute(dut_ip_addr + '/singleValue',
+                                '-value', dut_ip)
+
+        # Set local AS number
+        local_as_number = self.ixnet.getAttribute(obj, '-localAs2Bytes')
+        self.ixnet.setAttribute(local_as_number + '/singleValue',
+                                '-value', local_as)
+
+        if bgp_type:
+            # Set BGP type. If not specified, default value is using.
+            # Default type is "internal"
+            bgp_type_field = self.ixnet.getAttribute(obj, '-type')
+            self.ixnet.setAttribute(bgp_type_field + '/singleValue',
+                                    '-value', bgp_type)
+        self.ixnet.commit()
+        return obj