summaryrefslogtreecommitdiff
path: root/tests/TestSuite_vhost_user_live_migration.py
blob: 3f64244ac2ab30ff1da57c598025fba887475440 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# <COPYRIGHT_TAG>

import re
import time

from qemu_kvm import QEMUKvm
from test_case import TestCase
from exception import VirtDutInitException


class TestVhostUserLiveMigration(TestCase):

    def set_up_all(self):
        # verify at least two duts
        self.verify(len(self.duts) >= 2, "Insufficient duts for live migration!!!")

        # each dut required one ports
        self.dut_ports = self.dut.get_ports()
        # Verify that enough ports are available
        self.verify(len(self.dut_ports) >= 1, "Insufficient ports for testing")
        self.dut_port = self.dut_ports[0]
        dut_ip = self.dut.crb['My IP']
        self.host_tport = self.tester.get_local_port_bydut(self.dut_port, dut_ip)
        self.host_tintf = self.tester.get_interface(self.host_tport)

        self.backup_ports = self.duts[1].get_ports()
        # Verify that enough ports are available
        self.verify(len(self.backup_ports) >= 1, "Insufficient ports for testing")
        self.backup_port = self.backup_ports[0]
        # backup host ip will be used in migrate command
        self.backup_dutip = self.duts[1].crb['My IP']
        self.backup_tport = self.tester.get_local_port_bydut(self.backup_port, self.backup_dutip)
        self.backup_tintf = self.tester.get_interface(self.backup_tport)

        # Use testpmd as vhost-user application on host/backup server 
        self.vhost = "./x86_64-native-linuxapp-gcc/app/testpmd"
        self.vm_testpmd = "./%s/app/testpmd -c 0x3 -n 4 -- -i" % self.target
        self.virio_mac = "52:54:00:00:00:01"
        

        # flag for environment
        self.env_done = False

    def set_up(self):
        self.setup_vm_env()
        pass

    def bind_nic_driver(self, crb,  ports, driver=""):
        # modprobe vfio driver
        if driver == "vfio-pci":
            for port in ports:
                netdev = crb.ports_info[port]['port']
                driver = netdev.get_nic_driver()
                if driver != 'vfio-pci':
                    netdev.bind_driver(driver='vfio-pci')

        elif driver == "igb_uio":
            # igb_uio should insmod as default, no need to check
            for port in ports:
                netdev = crb.ports_info[port]['port']
                driver = netdev.get_nic_driver()
                if driver != 'igb_uio':
                    netdev.bind_driver(driver='igb_uio')
        else:
            for port in ports:
                netdev = crb.ports_info[port]['port']
                driver_now = netdev.get_nic_driver()
                if driver == "":
                    driver = netdev.default_driver
                if driver != driver_now:
                    netdev.bind_driver(driver=driver)

    def setup_vm_env(self, driver='default'):
        """
        Create testing environment on Host and Backup
        """
        if self.env_done:
            return

        # start vhost application on host and backup machines
        self.logger.info("Start vhost on host and backup host")
        for crb in self.duts[:2]:
            self.bind_nic_driver(crb, [crb.get_ports()[0]], driver="igb_uio")
            # start vhost app: testpmd, predict hugepage on both sockets
            base_dir = crb.base_dir.replace('~', '/root')
            crb.send_expect("rm -f %s/vhost-net" % base_dir, "# ")
            crb.send_expect("%s -c f -n 4 --socket-mem 512,512 --vdev 'eth_vhost0,iface=./vhost-net,queues=1' -- -i" % self.vhost, "testpmd> ",60)
            crb.send_expect("start", "testpmd> ")

        try:
            # set up host virtual machine
            self.host_vm = QEMUKvm(self.duts[0], 'host', 'vhost_user_live_migration')
            vhost_params = {}
            vhost_params['driver'] = 'vhost-user'
            # qemu command can't use ~
            base_dir = self.dut.base_dir.replace('~', '/root')
            vhost_params['opt_path'] = base_dir + '/vhost-net'
            vhost_params['opt_mac'] = self.virio_mac
            self.host_vm.set_vm_device(**vhost_params)

            self.logger.info("Start virtual machine on host")
            self.vm_host = self.host_vm.start()

            if self.vm_host is None:
                raise Exception("Set up host VM ENV failed!")

            self.host_serial = self.host_vm.connect_serial_port(name='vhost_user_live_migration')
            if self.host_serial is None:
                raise Exception("Connect host serial port failed!")

            self.logger.info("Start virtual machine on backup host")
            # set up backup virtual machine
            self.backup_vm = QEMUKvm(self.duts[1], 'backup', 'vhost_user_live_migration')
            vhost_params = {}
            vhost_params['driver'] = 'vhost-user'
            # qemu command can't use ~
            base_dir = self.dut.base_dir.replace('~', '/root')
            vhost_params['opt_path'] = base_dir + '/vhost-net'
            vhost_params['opt_mac'] = self.virio_mac
            self.backup_vm.set_vm_device(**vhost_params)

            # start qemu command
            self.backup_vm.start()

        except Exception as ex:
            if ex is VirtDutInitException:
                self.host_vm.stop()
                self.host_vm = None
                # no session created yet, call internal stop function
                self.backup_vm._stop_vm()
                self.backup_vm = None
            else:
                self.destroy_vm_env()
                raise Exception(ex)

        self.env_done = True

    def destroy_vm_env(self):
        # if environment has been destroyed, just skip
        if self.env_done is False:
            return

        if getattr(self, 'host_serial', None):
            if self.host_vm is not None:
                self.host_vm.close_serial_port()

        if getattr(self, 'backup_serial', None):
            if self.backup_serial is not None and self.backup_vm is not None:
                self.backup_vm.close_serial_port()


        if getattr(self, 'vm_host', None):
            if self.vm_host is not None:
                self.host_vm.stop()
                self.host_vm = None

        self.logger.info("Stop virtual machine on backup host")
        if getattr(self, 'vm_backup', None):
            if self.vm_backup is not None:
                self.vm_backup.kill_all()
                # backup vm dut has been initialized, destroy backup vm
                self.backup_vm.stop()
                self.backup_vm = None

        if getattr(self, 'backup_vm', None):
            # only qemu start, no session created
            if self.backup_vm is not None:
                self.backup_vm.stop()
                self.backup_vm = None

        # after vm stopped, stop vhost testpmd
        for crb in self.duts[:2]:
            crb.kill_all()

        for crb in self.duts[:2]:
            self.bind_nic_driver(crb, [crb.get_ports()[0]], driver="igb_uio")

        self.env_done = False

    def send_pkts(self, intf, number=0):
        """
        send packet from tester
        """
        sendp_fmt = "sendp([Ether(dst='%(DMAC)s')/IP()/UDP()/Raw('x'*18)], iface='%(INTF)s', count=%(COUNT)d)"
        sendp_cmd = sendp_fmt % {'DMAC': self.virio_mac, 'INTF': intf, 'COUNT': number}
        self.tester.scapy_append(sendp_cmd)
        self.tester.scapy_execute()
        # sleep 10 seconds for heavy load with backup host
        time.sleep(10)

    def verify_dpdk(self, tester_port, serial_session):
        num_pkts = 10

        stats_pat = re.compile("RX-packets: (\d+)")
        intf = self.tester.get_interface(tester_port)
        serial_session.send_expect("stop", "testpmd> ")
        serial_session.send_expect("set fwd rxonly", "testpmd> ")
        serial_session.send_expect("clear port stats all", "testpmd> ")
        serial_session.send_expect("start tx_first", "testpmd> ")

        # send packets from tester
        self.send_pkts(intf, number=num_pkts)

        out = serial_session.send_expect("show port stats 0", "testpmd> ")
        m = stats_pat.search(out)
        if m:
            num_received = int(m.group(1))
        else:
            num_received = 0

        self.logger.info("Verified %s packets recevied" % num_received)
        self.verify(num_received >= num_pkts, "Not receive packets as expected!!!")

    def verify_kernel(self, tester_port, vm_dut):
        """
        Function to verify packets received by virtIO
        """
        intf = self.tester.get_interface(tester_port)
        num_pkts = 10

        # get host interface
        vm_intf = vm_dut.ports_info[0]['port'].get_interface_name()
        # start tcpdump the interface
        vm_dut.send_expect("ifconfig %s up" % vm_intf, "# ")
        vm_dut.send_expect("tcpdump -i %s -P in -v" % vm_intf, "listening on")
        # wait for promisc on
        time.sleep(3)
        # send packets from tester
        self.send_pkts(intf, number=num_pkts)

        # killall tcpdump and verify packet received
        out = vm_dut.get_session_output(timeout=1)
        vm_dut.send_expect("^C", "# ")
        num = out.count('UDP')
        self.logger.info("Verified %s packets recevied" % num_pkts)
        self.verify(num == num_pkts, "Not receive packets as expected!!!")

    def test_migrate_with_kernel(self):
        """
        Verify migrate virtIO device from host to backup host,
        Verify before/in/after migration, device with kernel driver can receive packets
        """
        # bind virtio-net back to virtio-pci
        self.bind_nic_driver(self.vm_host, [self.vm_host.get_ports()[0]], driver="")
        # verify host virtio-net work fine
        self.verify_kernel(self.host_tport, self.vm_host)

        self.logger.info("Migrate host VM to backup host")
        # start live migration
        ret = self.host_vm.start_migration(self.backup_dutip, self.backup_vm.migrate_port)
        self.verify(ret, "Failed to migration, please check VM and qemu version")

        # make sure still can receive packets in migration process
        self.verify_kernel(self.host_tport, self.vm_host)

        self.logger.info("Waiting migration process done")
        # wait live migration done
        self.host_vm.wait_migration_done()

        # check vhost testpmd log after migration
        out = self.duts[0].get_session_output(timeout=1)
        self.verify("closed" in out, "Vhost Connection NOT closed on host")
        out = self.duts[1].get_session_output(timeout=1)
        self.verify("established" in out, "Device not ready on backup host")

        self.logger.info("Migration process done, then go to backup VM")
        # connected backup VM
        self.vm_backup = self.backup_vm.migrated_start()

        # make sure still can receive packets
        self.verify_kernel(self.backup_tport, self.vm_backup)

    def test_migrate_with_dpdk(self):
        # bind virtio-net to igb_uio
        self.bind_nic_driver(self.vm_host, [self.vm_host.get_ports()[0]], driver="igb_uio")

        # start testpmd on host vm
        base_dir = self.vm_host.base_dir.replace('~', '/root')
        self.host_serial.send_expect('cd %s' % base_dir, "# ")
        self.host_serial.send_expect(self.vm_testpmd, "testpmd> ")

        # verify testpmd receive packets
        self.verify_dpdk(self.host_tport, self.host_serial)

        self.logger.info("Migrate host VM to backup host")
        # start live migration
        
        ret = self.host_vm.start_migration(self.backup_dutip, self.backup_vm.migrate_port)
        self.verify(ret, "Failed to migration, please check VM and qemu version")
       
        # make sure still can receive packets in migration process
        self.verify_dpdk(self.host_tport, self.host_serial)

        self.logger.info("Waiting migration process done")
        # wait live migration done
        self.host_vm.wait_migration_done()

        # check vhost testpmd log after migration
        out = self.duts[0].get_session_output(timeout=1)
        self.verify("closed" in out, "Vhost Connection NOT closed on host")
        out = self.duts[1].get_session_output(timeout=1)
        self.verify("established" in out, "Device not ready on backup host")

        self.logger.info("Migration process done, then go to backup VM")
        time.sleep(5)

        # make sure still can receive packets
        self.backup_serial = self.backup_vm.connect_serial_port(name='vhost_user_live_migration', first=False)
        if self.backup_serial is None:
            raise Exception("Connect backup host serial port failed!")

        self.verify_dpdk(self.backup_tport, self.backup_serial)

        # quit testpmd
        self.backup_serial.send_expect("quit", "# ")

    def tear_down(self):
        self.destroy_vm_env()
        pass

    def tear_down_all(self):
        pass