Merge "tox: Split tests into separate tox environments"
[yardstick.git] / yardstick / network_services / vnf_generic / vnf / base.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 """ Base class implementation for generic vnf implementation """
15
16 import abc
17
18 import logging
19 import six
20
21 from yardstick.network_services.helpers.samplevnf_helper import PortPairs
22
23
24 LOG = logging.getLogger(__name__)
25
26
27 class QueueFileWrapper(object):
28     """ Class providing file-like API for talking with SSH connection """
29
30     def __init__(self, q_in, q_out, prompt):
31         self.q_in = q_in
32         self.q_out = q_out
33         self.closed = False
34         self.buf = []
35         self.bufsize = 20
36         self.prompt = prompt
37
38     def read(self, size):
39         """ read chunk from input queue """
40         if self.q_in.qsize() > 0 and size:
41             in_data = self.q_in.get()
42             return in_data
43
44     def write(self, chunk):
45         """ write chunk to output queue """
46         self.buf.append(chunk)
47         # flush on prompt or if we exceed bufsize
48
49         size = sum(len(c) for c in self.buf)
50         if self.prompt in chunk or size > self.bufsize:
51             out = ''.join(self.buf)
52             self.buf = []
53             self.q_out.put(out)
54
55     def close(self):
56         """ close multiprocessing queue """
57         pass
58
59     def clear(self):
60         """ clear queue """
61         while self.q_out.qsize() > 0:
62             self.q_out.get()
63
64
65 class VnfdHelper(dict):
66
67     def __init__(self, *args, **kwargs):
68         super(VnfdHelper, self).__init__(*args, **kwargs)
69         self.port_pairs = PortPairs(self['vdu'][0]['external-interface'])
70         # port num is not present until binding so we have to memoize
71         self._port_num_map = {}
72
73     @property
74     def mgmt_interface(self):
75         return self["mgmt-interface"]
76
77     @property
78     def vdu(self):
79         return self['vdu']
80
81     @property
82     def vdu0(self):
83         return self.vdu[0]
84
85     @property
86     def interfaces(self):
87         return self.vdu0['external-interface']
88
89     @property
90     def kpi(self):
91         return self['benchmark']['kpi']
92
93     def find_virtual_interface(self, **kwargs):
94         key, value = next(iter(kwargs.items()))
95         for interface in self.interfaces:
96             virtual_intf = interface["virtual-interface"]
97             if virtual_intf[key] == value:
98                 return interface
99         raise KeyError()
100
101     def find_interface(self, **kwargs):
102         key, value = next(iter(kwargs.items()))
103         for interface in self.interfaces:
104             if interface[key] == value:
105                 return interface
106         raise KeyError()
107
108     # hide dpdk_port_num key so we can abstract
109     def find_interface_by_port(self, port):
110         for interface in self.interfaces:
111             virtual_intf = interface["virtual-interface"]
112             # we have to convert to int to compare
113             if int(virtual_intf['dpdk_port_num']) == port:
114                 return interface
115         raise KeyError()
116
117     def port_num(self, port):
118         # we need interface name -> DPDK port num (PMD ID) -> LINK ID
119         # LINK ID -> PMD ID is governed by the port mask
120         """
121
122         :rtype: int
123         :type port: str
124         """
125         if isinstance(port, dict):
126             intf = port
127         else:
128             intf = self.find_interface(name=port)
129         return self._port_num_map.setdefault(intf["name"],
130                                              int(intf["virtual-interface"]["dpdk_port_num"]))
131
132     def port_nums(self, intfs):
133         return [self.port_num(i) for i in intfs]
134
135     def ports_iter(self):
136         for port_name in self.port_pairs.all_ports:
137             port_num = self.port_num(port_name)
138             yield port_name, port_num
139
140
141 class VNFObject(object):
142
143     # centralize network naming convention
144     UPLINK = PortPairs.UPLINK
145     DOWNLINK = PortPairs.DOWNLINK
146
147     def __init__(self, name, vnfd):
148         super(VNFObject, self).__init__()
149         self.name = name
150         self.vnfd_helper = VnfdHelper(vnfd)  # fixme: parse this into a structure
151
152
153 class GenericVNF(VNFObject):
154
155     """ Class providing file-like API for generic VNF implementation """
156     def __init__(self, name, vnfd):
157         super(GenericVNF, self).__init__(name, vnfd)
158         # List of statistics we can obtain from this VNF
159         # - ETSI MANO 6.3.1.1 monitoring_parameter
160         self.kpi = self._get_kpi_definition()
161         # Standard dictionary containing params like thread no, buffer size etc
162         self.config = {}
163         self.runs_traffic = False
164
165     def _get_kpi_definition(self):
166         """ Get list of KPIs defined in VNFD
167
168         :param vnfd:
169         :return: list of KPIs, e.g. ['throughput', 'latency']
170         """
171         return self.vnfd_helper.kpi
172
173     def instantiate(self, scenario_cfg, context_cfg):
174         """ Prepare VNF for operation and start the VNF process/VM
175
176         :param scenario_cfg:
177         :param context_cfg:
178         :return: True/False
179         """
180         raise NotImplementedError()
181
182     def wait_for_instantiate(self):
183         """ Wait for VNF to start
184
185         :return: True/False
186         """
187         raise NotImplementedError()
188
189     def terminate(self):
190         """ Kill all VNF processes
191
192         :return:
193         """
194         raise NotImplementedError()
195
196     def scale(self, flavor=""):
197         """
198
199         :param flavor:
200         :return:
201         """
202         raise NotImplementedError()
203
204     def collect_kpi(self):
205         """This method should return a dictionary containing the
206         selected KPI at a given point of time.
207
208         :return: {"kpi": value, "kpi2": value}
209         """
210         raise NotImplementedError()
211
212
213 @six.add_metaclass(abc.ABCMeta)
214 class GenericTrafficGen(GenericVNF):
215     """ Class providing file-like API for generic traffic generator """
216
217     def __init__(self, name, vnfd):
218         super(GenericTrafficGen, self).__init__(name, vnfd)
219         self.runs_traffic = True
220         self.traffic_finished = False
221
222     @abc.abstractmethod
223     def run_traffic(self, traffic_profile):
224         """Generate traffic on the wire according to the given params.
225
226         This method is non-blocking, returns immediately when traffic process
227         is running. Mandatory.
228
229         :param traffic_profile:
230         :return: True/False
231         """
232
233     @abc.abstractmethod
234     def terminate(self):
235         """After this method finishes, all traffic processes should stop.
236
237         Mandatory.
238
239         :return: True/False
240         """
241
242     def listen_traffic(self, traffic_profile):
243         """Listen to traffic with the given parameters.
244
245         Method is non-blocking, returns immediately when traffic process
246         is running. Optional.
247
248         :param traffic_profile:
249         :return: True/False
250         """
251         pass
252
253     def verify_traffic(self, traffic_profile):
254         """Verify captured traffic after it has ended.
255
256         Optional.
257
258         :param traffic_profile:
259         :return: dict
260         """
261         pass
262
263     def wait_for_instantiate(self):
264         """Wait for an instance to load.
265
266         Optional.
267
268         :return: True/False
269         """
270         pass