Merge "Dashboard with Network and Platform NFVi metrics" into stable/gambia
[yardstick.git] / yardstick / network_services / traffic_profile / ixia_rfc2544.py
1 # Copyright (c) 2016-2017 Intel Corporation
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import logging
16
17 from yardstick.common import utils
18 from yardstick.network_services.traffic_profile import base as tp_base
19 from yardstick.network_services.traffic_profile import trex_traffic_profile
20
21
22 LOG = logging.getLogger(__name__)
23
24
25 class IXIARFC2544Profile(trex_traffic_profile.TrexProfile):
26
27     UPLINK = 'uplink'
28     DOWNLINK = 'downlink'
29     DROP_PERCENT_ROUND = 6
30     RATE_ROUND = 5
31     STATUS_SUCCESS = "Success"
32     STATUS_FAIL = "Failure"
33
34     def __init__(self, yaml_data):
35         super(IXIARFC2544Profile, self).__init__(yaml_data)
36         self.rate = self.config.frame_rate
37         self.rate_unit = self.config.rate_unit
38
39     def _get_ip_and_mask(self, ip_range):
40         _ip_range = ip_range.split('-')
41         if len(_ip_range) == 1:
42             return _ip_range[0], None
43
44         mask = utils.get_mask_from_ip_range(_ip_range[0], _ip_range[1])
45         return _ip_range[0], mask
46
47     def _get_fixed_and_mask(self, port_range):
48         _port_range = str(port_range).split('-')
49         if len(_port_range) == 1:
50             return int(_port_range[0]), 0
51
52         return int(_port_range[0]), int(_port_range[1])
53
54     def _get_ixia_traffic_profile(self, profile_data, mac=None):
55         mac = {} if mac is None else mac
56         result = {}
57         for traffickey, values in profile_data.items():
58             if not traffickey.startswith((self.UPLINK, self.DOWNLINK)):
59                 continue
60
61             try:
62                 # values should be single-item dict, so just grab the first item
63                 try:
64                     key, value = next(iter(values.items()))
65                 except StopIteration:
66                     result[traffickey] = {}
67                     continue
68
69                 port_id = value.get('id', 1)
70                 port_index = port_id - 1
71
72                 if value.get('outer_l3v4'):
73                     ip = value['outer_l3v4']
74                     src_key, dst_key = 'srcip4', 'dstip4'
75                 else:
76                     ip = value['outer_l3v6']
77                     src_key, dst_key = 'srcip6', 'dstip6'
78
79                 srcip, srcmask = self._get_ip_and_mask(ip[src_key])
80                 dstip, dstmask = self._get_ip_and_mask(ip[dst_key])
81
82                 outer_l4 = value.get('outer_l4')
83                 src_port, src_port_mask = self._get_fixed_and_mask(outer_l4['srcport'])
84                 dst_port, dst_port_mask = self._get_fixed_and_mask(outer_l4['dstport'])
85                 result[traffickey] = {
86                     'bidir': False,
87                     'id': port_id,
88                     'rate': self.rate,
89                     'rate_unit': self.rate_unit,
90                     'outer_l2': {
91                         'framesize': value['outer_l2']['framesize'],
92                         'framesPerSecond': True,
93                         'QinQ': value['outer_l2'].get('QinQ'),
94                         'srcmac': mac['src_mac_{}'.format(port_index)],
95                         'dstmac': mac['dst_mac_{}'.format(port_index)],
96                     },
97                     'outer_l3': {
98                         'count': ip['count'],
99                         'dscp': ip['dscp'],
100                         'ttl': ip['ttl'],
101                         'srcseed': ip.get('srcseed', 1),
102                         'dstseed': ip.get('dstseed', 1),
103                         'srcip': srcip,
104                         'dstip': dstip,
105                         'srcmask': srcmask,
106                         'dstmask': dstmask,
107                         'type': key,
108                         'proto': ip['proto'],
109                     },
110                     'outer_l4': {
111                         'srcport': src_port,
112                         'dstport': dst_port,
113                         'srcportmask': src_port_mask,
114                         'dstportmask': dst_port_mask,
115                         'count': outer_l4['count'],
116                         'seed': outer_l4.get('seed', 1)
117                     }
118
119                 }
120             except KeyError:
121                 continue
122
123         return result
124
125     def _ixia_traffic_generate(self, traffic, ixia_obj):
126         ixia_obj.update_frame(traffic, self.config.duration)
127         ixia_obj.update_ip_packet(traffic)
128         ixia_obj.update_l4(traffic)
129         ixia_obj.start_traffic()
130
131     def update_traffic_profile(self, traffic_generator):
132         def port_generator():
133             for vld_id, intfs in sorted(traffic_generator.networks.items()):
134                 if not vld_id.startswith((self.UPLINK, self.DOWNLINK)):
135                     continue
136                 profile_data = self.params.get(vld_id)
137                 if not profile_data:
138                     continue
139                 self.profile_data = profile_data
140                 self.full_profile.update({vld_id: self.profile_data})
141                 for intf in intfs:
142                     yield traffic_generator.vnfd_helper.port_num(intf)
143
144         self.ports = [port for port in port_generator()]
145
146     def execute_traffic(self, traffic_generator, ixia_obj=None, mac=None):
147         mac = {} if mac is None else mac
148         first_run = self.first_run
149         if self.first_run:
150             self.first_run = False
151             self.full_profile = {}
152             self.pg_id = 0
153             self.update_traffic_profile(traffic_generator)
154             self.max_rate = self.rate
155             self.min_rate = 0.0
156         else:
157             self.rate = round(float(self.max_rate + self.min_rate) / 2.0,
158                               self.RATE_ROUND)
159
160         traffic = self._get_ixia_traffic_profile(self.full_profile, mac)
161         self._ixia_traffic_generate(traffic, ixia_obj)
162         return first_run
163
164     def get_drop_percentage(self, samples, tol_min, tolerance, precision,
165                             first_run=False):
166         completed = False
167         drop_percent = 100
168         num_ifaces = len(samples)
169         duration = self.config.duration
170         in_packets_sum = sum(
171             [samples[iface]['in_packets'] for iface in samples])
172         out_packets_sum = sum(
173             [samples[iface]['out_packets'] for iface in samples])
174         rx_throughput = round(float(in_packets_sum) / duration, 3)
175         tx_throughput = round(float(out_packets_sum) / duration, 3)
176         packet_drop = abs(out_packets_sum - in_packets_sum)
177
178         try:
179             drop_percent = round(
180                 (packet_drop / float(out_packets_sum)) * 100,
181                 self.DROP_PERCENT_ROUND)
182         except ZeroDivisionError:
183             LOG.info('No traffic is flowing')
184
185         if first_run:
186             completed = True if drop_percent <= tolerance else False
187         if (first_run and
188                 self.rate_unit == tp_base.TrafficProfileConfig.RATE_FPS):
189             self.rate = float(out_packets_sum) / duration / num_ifaces
190
191         if drop_percent > tolerance:
192             self.max_rate = self.rate
193         elif drop_percent < tol_min:
194             self.min_rate = self.rate
195         else:
196             completed = True
197
198         LOG.debug("tolerance=%s, tolerance_precision=%s drop_percent=%s "
199                   "completed=%s", tolerance, precision, drop_percent,
200                   completed)
201
202         latency_ns_avg = float(
203             sum([samples[iface]['Store-Forward_Avg_latency_ns']
204             for iface in samples])) / num_ifaces
205         latency_ns_min = float(
206             sum([samples[iface]['Store-Forward_Min_latency_ns']
207             for iface in samples])) / num_ifaces
208         latency_ns_max = float(
209             sum([samples[iface]['Store-Forward_Max_latency_ns']
210             for iface in samples])) / num_ifaces
211
212         samples['Status'] = self.STATUS_FAIL
213         if round(drop_percent, precision) <= tolerance:
214             samples['Status'] = self.STATUS_SUCCESS
215
216         samples['TxThroughput'] = tx_throughput
217         samples['RxThroughput'] = rx_throughput
218         samples['DropPercentage'] = drop_percent
219         samples['latency_ns_avg'] = latency_ns_avg
220         samples['latency_ns_min'] = latency_ns_min
221         samples['latency_ns_max'] = latency_ns_max
222
223         return completed, samples