Merge "Add OvS 2.8.1 support into SA context"
[yardstick.git] / yardstick / benchmark / contexts / node.py
1 ##############################################################################
2 # Copyright (c) 2015 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 import subprocess
11 import os
12 import collections
13 import logging
14 import tempfile
15
16 import six
17 import pkg_resources
18
19 from yardstick import ssh
20 from yardstick.benchmark import contexts
21 from yardstick.benchmark.contexts.base import Context
22 from yardstick.common.constants import ANSIBLE_DIR, YARDSTICK_ROOT_PATH
23 from yardstick.common.ansible_common import AnsibleCommon
24 from yardstick.common.exceptions import ContextUpdateCollectdForNodeError
25
26 LOG = logging.getLogger(__name__)
27
28 DEFAULT_DISPATCH = 'script'
29
30
31 class NodeContext(Context):
32     """Class that handle nodes info"""
33
34     __context_type__ = contexts.CONTEXT_NODE
35
36     def __init__(self):
37         self.file_path = None
38         self.nodes = []
39         self.networks = {}
40         self.controllers = []
41         self.computes = []
42         self.baremetals = []
43         self.env = {}
44         self.attrs = {}
45         self.DISPATCH_TYPES = {
46             "ansible": self._dispatch_ansible,
47             "script": self._dispatch_script,
48         }
49         super(NodeContext, self).__init__()
50
51     def init(self, attrs):
52         """initializes itself from the supplied arguments"""
53         super(NodeContext, self).init(attrs)
54
55         cfg = self.read_pod_file(attrs)
56
57         self.env = attrs.get('env', {})
58         self.attrs = attrs
59         LOG.debug("Env: %r", self.env)
60
61         # add optional static network definition
62         self.networks.update(cfg.get("networks", {}))
63
64     def deploy(self):
65         config_type = self.env.get('type', DEFAULT_DISPATCH)
66         self.DISPATCH_TYPES[config_type]("setup")
67
68     def undeploy(self):
69         config_type = self.env.get('type', DEFAULT_DISPATCH)
70         self.DISPATCH_TYPES[config_type]("teardown")
71         super(NodeContext, self).undeploy()
72
73     def _dispatch_script(self, key):
74         steps = self.env.get(key, [])
75         for step in steps:
76             for host, info in step.items():
77                 self._execute_script(host, info)
78
79     def _dispatch_ansible(self, key):
80         try:
81             playbooks = self.env[key]
82         except KeyError:
83             pass
84         else:
85             self._do_ansible_job(playbooks)
86
87     def _do_ansible_job(self, playbooks):
88         self.ansible_exec = AnsibleCommon(nodes=self.nodes,
89                                           test_vars=self.env)
90         # playbooks relative to ansible dir
91         # playbooks can also be a list of playbooks
92         self.ansible_exec.gen_inventory_ini_dict()
93         if isinstance(playbooks, six.string_types):
94             playbooks = [playbooks]
95         playbooks = [self.fix_ansible_path(playbook) for playbook in playbooks]
96
97         tmpdir = tempfile.mkdtemp(prefix='ansible-')
98         self.ansible_exec.execute_ansible(playbooks, tmpdir,
99                                           verbose=self.env.get("verbose",
100                                                                False))
101
102     def fix_ansible_path(self, playbook):
103         if not os.path.isabs(playbook):
104             #  make relative paths absolute in ANSIBLE_DIR
105             playbook = os.path.join(ANSIBLE_DIR, playbook)
106         return playbook
107
108     def _get_physical_nodes(self):
109         return self.nodes
110
111     def _get_physical_node_for_server(self, server_name):
112
113         node_name, context_name = self.split_host_name(server_name)
114
115         if context_name is None or self.name != context_name:
116             return None
117
118         for n in (n for n in self.nodes if n["name"] == node_name):
119             return "{}.{}".format(n["name"], self._name)
120
121         return None
122
123     def update_collectd_options_for_node(self, options, attr_name):
124         node_name, _ = self.split_host_name(attr_name)
125
126         matching_nodes = (n for n in self.nodes if n["name"] == node_name)
127         try:
128             node = next(matching_nodes)
129         except StopIteration:
130             raise ContextUpdateCollectdForNodeError(attr_name=attr_name)
131
132         node["collectd"] = options
133
134     def _get_server(self, attr_name):
135         """lookup server info by name from context
136         attr_name: a name for a server listed in nodes config file
137         """
138         node_name, name = self.split_host_name(attr_name)
139         if name is None or self.name != name:
140             return None
141
142         matching_nodes = (n for n in self.nodes if n["name"] == node_name)
143
144         try:
145             # A clone is created in order to avoid affecting the
146             # original one.
147             node = dict(next(matching_nodes))
148         except StopIteration:
149             return None
150
151         try:
152             duplicate = next(matching_nodes)
153         except StopIteration:
154             pass
155         else:
156             raise ValueError("Duplicate nodes!!! Nodes: %s %s" %
157                              (node, duplicate))
158
159         node["name"] = attr_name
160         node.setdefault("interfaces", {})
161         return node
162
163     def _get_network(self, attr_name):
164         if not isinstance(attr_name, collections.Mapping):
165             network = self.networks.get(attr_name)
166
167         else:
168             # Don't generalize too much  Just support vld_id
169             vld_id = attr_name.get('vld_id', {})
170             # for node context networks are dicts
171             iter1 = (n for n in self.networks.values() if n.get('vld_id') == vld_id)
172             network = next(iter1, None)
173
174         if network is None:
175             return None
176
177         result = {
178             # name is required
179             "name": network["name"],
180             "vld_id": network.get("vld_id"),
181             "segmentation_id": network.get("segmentation_id"),
182             "network_type": network.get("network_type"),
183             "physical_network": network.get("physical_network"),
184         }
185         return result
186
187     def _execute_script(self, node_name, info):
188         if node_name == 'local':
189             self._execute_local_script(info)
190         else:
191             self._execute_remote_script(node_name, info)
192
193     def _execute_remote_script(self, node_name, info):
194         prefix = self.env.get('prefix', '')
195         script, options = self._get_script(info)
196
197         script_file = pkg_resources.resource_filename(prefix, script)
198
199         self._get_client(node_name)
200         self.client._put_file_shell(script_file, '~/{}'.format(script))
201
202         cmd = 'sudo bash {} {}'.format(script, options)
203         status, _, stderr = self.client.execute(cmd)
204         if status:
205             raise RuntimeError(stderr)
206
207     def _execute_local_script(self, info):
208         script, options = self._get_script(info)
209         script = os.path.join(YARDSTICK_ROOT_PATH, script)
210         cmd = ['bash', script, options]
211
212         p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
213         LOG.debug('\n%s', p.communicate()[0])
214
215     def _get_script(self, info):
216         return info.get('script'), info.get('options', '')
217
218     def _get_client(self, node_name):
219         node = self._get_node_info(node_name.strip())
220
221         if node is None:
222             raise SystemExit('No such node')
223
224         self.client = ssh.SSH.from_node(node, defaults={'user': 'ubuntu'})
225
226         self.client.wait(timeout=600)
227
228     def _get_node_info(self, name):
229         return next((n for n in self.nodes if n['name'].strip() == name))