Merge "Change tc_trex files to execute standalone tests"
[yardstick.git] / yardstick / benchmark / scenarios / lib / migrate.py
1 # ############################################################################
2 # Copyright (c) 2017 Huawei Technologies Co.,Ltd and others.
3 #
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 # ############################################################################
9
10 from __future__ import print_function
11 from __future__ import absolute_import
12
13 import logging
14 import subprocess
15 import threading
16 import time
17
18 from datetime import datetime
19
20
21 from yardstick.common import openstack_utils
22 from yardstick.common.utils import change_obj_to_dict
23 from yardstick.benchmark.scenarios import base
24
25 LOG = logging.getLogger(__name__)
26
27 TIMEOUT = 0.05
28 PACKAGE_SIZE = 64
29
30
31 try:
32     import ping
33 except ImportError:
34     # temp fix for ping module import error on Python3
35     # we need to replace the ping module anyway
36     import mock
37     ping = mock.MagicMock()
38
39
40 class Migrate(base.Scenario):       # pragma: no cover
41     """
42     Execute a live migration for two hosts
43
44     """
45
46     __scenario_type__ = "Migrate"
47
48     def __init__(self, scenario_cfg, context_cfg):
49         self.scenario_cfg = scenario_cfg
50         self.context_cfg = context_cfg
51         self.options = self.scenario_cfg.get('options', {})
52
53         self.nova_client = openstack_utils.get_nova_client()
54
55     def run(self, result):
56         default_instance_id = self.options.get('server', {}).get('id', '')
57         instance_id = self.options.get('server_id', default_instance_id)
58         LOG.info('Instance id is %s', instance_id)
59
60         target_host = self.options.get('host')
61         LOG.info('Target host is %s', target_host)
62
63         instance_ip = self.options.get('server_ip')
64         if instance_ip:
65             LOG.info('Instance ip is %s', instance_ip)
66
67             self._ping_until_connected(instance_ip)
68             LOG.info('Instance is connected')
69
70             LOG.debug('Start to ping instance')
71             ping_thread = self._do_ping_task(instance_ip)
72
73         keys = self.scenario_cfg.get('output', '').split()
74         try:
75             LOG.info('Start to migrate')
76             self._do_migrate(instance_id, target_host)
77         except Exception as e:
78             return self._push_to_outputs(keys, [1, str(e).split('.')[0]])
79         else:
80             migrate_time = self._get_migrate_time(instance_id)
81             LOG.info('Migration time is %s s', migrate_time)
82
83             current_host = self._get_current_host_name(instance_id)
84             LOG.info('Current host is %s', current_host)
85             if current_host.strip() != target_host.strip():
86                 LOG.error('current_host not equal to target_host')
87                 values = [1, 'current_host not equal to target_host']
88                 return self._push_to_outputs(keys, values)
89
90         if instance_ip:
91             ping_thread.flag = False
92             ping_thread.join()
93
94             downtime = ping_thread.get_delay()
95             LOG.info('Downtime is %s s', downtime)
96
97             values = [0, migrate_time, downtime]
98             return self._push_to_outputs(keys, values)
99         else:
100             values = [0, migrate_time]
101             return self._push_to_outputs(keys, values)
102
103     def _do_migrate(self, server_id, target_host):
104
105         cmd = ['nova', 'live-migration', server_id, target_host]
106         p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
107         p.communicate()
108
109     def _ping_until_connected(self, instance_ip):
110         for i in range(3000):
111             res = ping.do_one(instance_ip, TIMEOUT, PACKAGE_SIZE)
112             if res:
113                 break
114
115     def _do_ping_task(self, instance_ip):
116         ping_thread = PingThread(instance_ip)
117         ping_thread.start()
118         return ping_thread
119
120     def _get_current_host_name(self, server_id):
121
122         return change_obj_to_dict(self.nova_client.servers.get(server_id))['OS-EXT-SRV-ATTR:host']
123
124     def _get_migrate_time(self, server_id):
125         while True:
126             status = self.nova_client.servers.get(server_id).status.lower()
127             if status == 'migrating':
128                 start_time = datetime.now()
129                 break
130         LOG.debug('Instance status change to MIGRATING')
131
132         while True:
133             status = self.nova_client.servers.get(server_id).status.lower()
134             if status == 'active':
135                 end_time = datetime.now()
136                 break
137             if status == 'error':
138                 LOG.error('Instance status is ERROR')
139                 raise RuntimeError('The instance status is error')
140         LOG.debug('Instance status change to ACTIVE')
141
142         duration = end_time - start_time
143         return duration.seconds + duration.microseconds * 1.0 / 1e6
144
145
146 class PingThread(threading.Thread):     # pragma: no cover
147
148     def __init__(self, target):
149         super(PingThread, self).__init__()
150         self.target = target
151         self.flag = True
152         self.delay = 0.0
153
154     def run(self):
155         count = 0
156         while self.flag:
157             res = ping.do_one(self.target, TIMEOUT, PACKAGE_SIZE)
158             if not res:
159                 count += 1
160             time.sleep(0.01)
161         self.delay = (TIMEOUT + 0.01) * count
162
163     def get_delay(self):
164         return self.delay