blob: 5a29aa1eb184e3ba6690be5b315c5751ff7079a2 [file] [log] [blame]
Itzik Browne67ebb52016-05-15 05:34:41 +00001# Copyright 2016 Red Hat, Inc.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
Chandan Kumarc125fd12017-11-15 19:41:01 +053015import subprocess
Itzik Browne67ebb52016-05-15 05:34:41 +000016
Federico Ressibf877c82018-08-22 08:36:37 +020017from debtcollector import removals
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090018import netaddr
Assaf Muller92fdc782018-05-31 10:32:47 -040019from neutron_lib.api import validators
20from neutron_lib import constants as neutron_lib_constants
Alex Stafeyevc4d9c352016-12-12 04:13:33 -050021from oslo_log import log
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +030022from tempest.common.utils import net_utils
Itzik Browne67ebb52016-05-15 05:34:41 +000023from tempest.common import waiters
Itzik Browne67ebb52016-05-15 05:34:41 +000024from tempest.lib.common.utils import data_utils
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090025from tempest.lib.common.utils import test_utils
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -050026from tempest.lib import exceptions as lib_exc
Itzik Browne67ebb52016-05-15 05:34:41 +000027
Chandan Kumar667d3d32017-09-22 12:24:06 +053028from neutron_tempest_plugin.api import base as base_api
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +020029from neutron_tempest_plugin.common import shell
Chandan Kumar667d3d32017-09-22 12:24:06 +053030from neutron_tempest_plugin.common import ssh
31from neutron_tempest_plugin import config
32from neutron_tempest_plugin.scenario import constants
Itzik Browne67ebb52016-05-15 05:34:41 +000033
34CONF = config.CONF
Itzik Browne67ebb52016-05-15 05:34:41 +000035
Alex Stafeyevc4d9c352016-12-12 04:13:33 -050036LOG = log.getLogger(__name__)
37
Itzik Browne67ebb52016-05-15 05:34:41 +000038
39class BaseTempestTestCase(base_api.BaseNetworkTest):
Itzik Browne67ebb52016-05-15 05:34:41 +000040
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000041 def create_server(self, flavor_ref, image_ref, key_name, networks,
Roee Agiman6a0a18a2017-11-16 11:51:56 +020042 **kwargs):
Itzik Brownbac51dc2016-10-31 12:25:04 +000043 """Create a server using tempest lib
Brian Haleyaee61ac2018-10-09 20:00:27 -040044
Itzik Brownbac51dc2016-10-31 12:25:04 +000045 All the parameters are the ones used in Compute API
Roee Agiman6a0a18a2017-11-16 11:51:56 +020046 * - Kwargs that require admin privileges
Itzik Brownbac51dc2016-10-31 12:25:04 +000047
48 Args:
49 flavor_ref(str): The flavor of the server to be provisioned.
50 image_ref(str): The image of the server to be provisioned.
51 key_name(str): SSH key to to be used to connect to the
52 provisioned server.
53 networks(list): List of dictionaries where each represent
54 an interface to be attached to the server. For network
55 it should be {'uuid': network_uuid} and for port it should
56 be {'port': port_uuid}
Roee Agiman6a0a18a2017-11-16 11:51:56 +020057 kwargs:
Itzik Brownbac51dc2016-10-31 12:25:04 +000058 name(str): Name of the server to be provisioned.
59 security_groups(list): List of dictionaries where
60 the keys is 'name' and the value is the name of
61 the security group. If it's not passed the default
62 security group will be used.
Roee Agiman6a0a18a2017-11-16 11:51:56 +020063 availability_zone(str)*: The availability zone that
64 the instance will be in.
65 You can request a specific az without actually creating one,
66 Just pass 'X:Y' where X is the default availability
67 zone, and Y is the compute host name.
Itzik Brownbac51dc2016-10-31 12:25:04 +000068 """
69
Jakub Libosvarffd9b912017-11-16 09:54:14 +000070 kwargs.setdefault('name', data_utils.rand_name('server-test'))
Itzik Brownbac51dc2016-10-31 12:25:04 +000071
Jakub Libosvarffd9b912017-11-16 09:54:14 +000072 # We cannot use setdefault() here because caller could have passed
73 # security_groups=None and we don't want to pass None to
74 # client.create_server()
75 if not kwargs.get('security_groups'):
76 kwargs['security_groups'] = [{'name': 'default'}]
Roee Agiman6a0a18a2017-11-16 11:51:56 +020077
Jakub Libosvarffd9b912017-11-16 09:54:14 +000078 client = self.os_primary.servers_client
79 if kwargs.get('availability_zone'):
Roee Agiman6a0a18a2017-11-16 11:51:56 +020080 client = self.os_admin.servers_client
Roee Agiman6a0a18a2017-11-16 11:51:56 +020081
Jakub Libosvarffd9b912017-11-16 09:54:14 +000082 server = client.create_server(
83 flavorRef=flavor_ref,
84 imageRef=image_ref,
85 key_name=key_name,
86 networks=networks,
87 **kwargs)
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000088
89 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Roee Agiman6a0a18a2017-11-16 11:51:56 +020090 waiters.wait_for_server_termination,
91 client,
92 server['server']['id'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000093 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Roee Agiman6a0a18a2017-11-16 11:51:56 +020094 client.delete_server,
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000095 server['server']['id'])
Itzik Browne67ebb52016-05-15 05:34:41 +000096 return server
97
98 @classmethod
Jens Harbott54357632017-11-21 11:47:06 +000099 def create_secgroup_rules(cls, rule_list, secgroup_id=None,
100 client=None):
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200101 client = client or cls.os_primary.network_client
Itzik Browne67ebb52016-05-15 05:34:41 +0000102 if not secgroup_id:
103 sgs = client.list_security_groups()['security_groups']
104 for sg in sgs:
105 if sg['name'] == constants.DEFAULT_SECURITY_GROUP:
106 secgroup_id = sg['id']
107 break
108
Itzik Brown1ef813a2016-06-06 12:56:21 +0000109 for rule in rule_list:
110 direction = rule.pop('direction')
111 client.create_security_group_rule(
112 direction=direction,
113 security_group_id=secgroup_id,
114 **rule)
115
116 @classmethod
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200117 def create_loginable_secgroup_rule(cls, secgroup_id=None,
118 client=None):
Itzik Brown1ef813a2016-06-06 12:56:21 +0000119 """This rule is intended to permit inbound ssh
120
121 Allowing ssh traffic traffic from all sources, so no group_id is
122 provided.
123 Setting a group_id would only permit traffic from ports
124 belonging to the same security group.
125 """
Federico Ressi4c590d72018-10-10 14:01:08 +0200126 return cls.create_security_group_rule(
127 security_group_id=secgroup_id,
128 client=client,
129 protocol=neutron_lib_constants.PROTO_NAME_TCP,
130 direction=neutron_lib_constants.INGRESS_DIRECTION,
131 port_range_min=22,
132 port_range_max=22)
Itzik Browne67ebb52016-05-15 05:34:41 +0000133
134 @classmethod
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200135 def create_pingable_secgroup_rule(cls, secgroup_id=None,
136 client=None):
Federico Ressi4c590d72018-10-10 14:01:08 +0200137 """This rule is intended to permit inbound ping
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900138
Federico Ressi4c590d72018-10-10 14:01:08 +0200139 """
140 return cls.create_security_group_rule(
141 security_group_id=secgroup_id, client=client,
142 protocol=neutron_lib_constants.PROTO_NAME_ICMP,
143 direction=neutron_lib_constants.INGRESS_DIRECTION)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900144
145 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300146 def create_router_by_client(cls, is_admin=False, **kwargs):
147 kwargs.update({'router_name': data_utils.rand_name('router'),
148 'admin_state_up': True,
149 'external_network_id': CONF.network.public_network_id})
150 if not is_admin:
151 router = cls.create_router(**kwargs)
152 else:
153 router = cls.create_admin_router(**kwargs)
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500154 LOG.debug("Created router %s", router['name'])
Itzik Browne67ebb52016-05-15 05:34:41 +0000155 cls.routers.append(router)
156 return router
157
Federico Ressibf877c82018-08-22 08:36:37 +0200158 @removals.remove(version='Stein',
159 message="Please use create_floatingip method instead of "
160 "create_and_associate_floatingip.")
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200161 def create_and_associate_floatingip(self, port_id, client=None):
162 client = client or self.os_primary.network_client
Federico Ressibf877c82018-08-22 08:36:37 +0200163 return self.create_floatingip(port_id=port_id, client=client)
Itzik Browne67ebb52016-05-15 05:34:41 +0000164
Hongbin Lu965b03d2018-04-25 22:32:30 +0000165 def create_interface(cls, server_id, port_id, client=None):
166 client = client or cls.os_primary.interfaces_client
167 body = client.create_interface(server_id, port_id=port_id)
168 return body['interfaceAttachment']
169
170 def delete_interface(cls, server_id, port_id, client=None):
171 client = client or cls.os_primary.interfaces_client
172 client.delete_interface(server_id, port_id=port_id)
173
Brian Haleyd11f4ec2019-08-13 12:09:57 -0400174 def setup_network_and_server(self, router=None, server_name=None,
175 network=None, **kwargs):
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300176 """Create network resources and a server.
Itzik Brownbac51dc2016-10-31 12:25:04 +0000177
178 Creating a network, subnet, router, keypair, security group
179 and a server.
180 """
Assaf Mullerd54ae6c2018-05-31 11:38:00 -0400181 self.network = network or self.create_network()
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000182 LOG.debug("Created network %s", self.network['name'])
183 self.subnet = self.create_subnet(self.network)
184 LOG.debug("Created subnet %s", self.subnet['id'])
Itzik Brown1ef813a2016-06-06 12:56:21 +0000185
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400186 secgroup = self.os_primary.network_client.create_security_group(
Chandan Kumarc125fd12017-11-15 19:41:01 +0530187 name=data_utils.rand_name('secgroup'))
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500188 LOG.debug("Created security group %s",
189 secgroup['security_group']['name'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000190 self.security_groups.append(secgroup['security_group'])
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300191 if not router:
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000192 router = self.create_router_by_client(**kwargs)
193 self.create_router_interface(router['id'], self.subnet['id'])
194 self.keypair = self.create_keypair()
195 self.create_loginable_secgroup_rule(
Itzik Brownbac51dc2016-10-31 12:25:04 +0000196 secgroup_id=secgroup['security_group']['id'])
Assaf Muller92fdc782018-05-31 10:32:47 -0400197
198 server_kwargs = {
199 'flavor_ref': CONF.compute.flavor_ref,
200 'image_ref': CONF.compute.image_ref,
201 'key_name': self.keypair['name'],
202 'networks': [{'uuid': self.network['id']}],
203 'security_groups': [{'name': secgroup['security_group']['name']}],
204 }
205 if server_name is not None:
206 server_kwargs['name'] = server_name
207
208 self.server = self.create_server(**server_kwargs)
Federico Ressie7417b72018-05-30 05:50:58 +0200209 self.wait_for_server_active(self.server['server'])
Jakub Libosvar1345d9d2017-06-09 13:59:05 +0000210 self.port = self.client.list_ports(network_id=self.network['id'],
211 device_id=self.server[
212 'server']['id'])['ports'][0]
Federico Ressibf877c82018-08-22 08:36:37 +0200213 self.fip = self.create_floatingip(port=self.port)
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500214
215 def check_connectivity(self, host, ssh_user, ssh_key, servers=None):
216 ssh_client = ssh.Client(host, ssh_user, pkey=ssh_key)
217 try:
218 ssh_client.test_connection_auth()
219 except lib_exc.SSHTimeout as ssh_e:
220 LOG.debug(ssh_e)
221 self._log_console_output(servers)
222 raise
223
224 def _log_console_output(self, servers=None):
225 if not CONF.compute_feature_enabled.console_output:
226 LOG.debug('Console output not supported, cannot log')
227 return
228 if not servers:
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400229 servers = self.os_primary.servers_client.list_servers()
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500230 servers = servers['servers']
231 for server in servers:
232 try:
233 console_output = (
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400234 self.os_primary.servers_client.get_console_output(
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500235 server['id'])['output'])
236 LOG.debug('Console output for %s\nbody=\n%s',
237 server['id'], console_output)
238 except lib_exc.NotFound:
239 LOG.debug("Server %s disappeared(deleted) while looking "
240 "for the console log", server['id'])
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900241
LIU Yulong68ab2452019-05-18 10:19:49 +0800242 def _check_remote_connectivity(self, source, dest, count,
243 should_succeed=True,
Assaf Muller92fdc782018-05-31 10:32:47 -0400244 nic=None, mtu=None, fragmentation=True,
245 timeout=None):
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900246 """check ping server via source ssh connection
247
248 :param source: RemoteClient: an ssh connection from which to ping
249 :param dest: and IP to ping against
LIU Yulong68ab2452019-05-18 10:19:49 +0800250 :param count: Number of ping packet(s) to send
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900251 :param should_succeed: boolean should ping succeed or not
252 :param nic: specific network interface to ping from
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300253 :param mtu: mtu size for the packet to be sent
254 :param fragmentation: Flag for packet fragmentation
LIU Yulong68ab2452019-05-18 10:19:49 +0800255 :param timeout: Timeout for all ping packet(s) to succeed
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900256 :returns: boolean -- should_succeed == ping
257 :returns: ping is false if ping failed
258 """
LIU Yulong68ab2452019-05-18 10:19:49 +0800259 def ping_host(source, host, count,
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300260 size=CONF.validation.ping_size, nic=None, mtu=None,
261 fragmentation=True):
Assaf Muller92fdc782018-05-31 10:32:47 -0400262 IP_VERSION_4 = neutron_lib_constants.IP_VERSION_4
263 IP_VERSION_6 = neutron_lib_constants.IP_VERSION_6
264
265 # Use 'ping6' for IPv6 addresses, 'ping' for IPv4 and hostnames
266 ip_version = (
267 IP_VERSION_6 if netaddr.valid_ipv6(host) else IP_VERSION_4)
268 cmd = (
269 'ping6' if ip_version == IP_VERSION_6 else 'ping')
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900270 if nic:
271 cmd = 'sudo {cmd} -I {nic}'.format(cmd=cmd, nic=nic)
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300272 if mtu:
273 if not fragmentation:
274 cmd += ' -M do'
275 size = str(net_utils.get_ping_payload_size(
Assaf Muller92fdc782018-05-31 10:32:47 -0400276 mtu=mtu, ip_version=ip_version))
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900277 cmd += ' -c{0} -w{0} -s{1} {2}'.format(count, size, host)
278 return source.exec_command(cmd)
279
280 def ping_remote():
281 try:
LIU Yulong68ab2452019-05-18 10:19:49 +0800282 result = ping_host(source, dest, count, nic=nic, mtu=mtu,
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300283 fragmentation=fragmentation)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900284
285 except lib_exc.SSHExecCommandFailed:
286 LOG.warning('Failed to ping IP: %s via a ssh connection '
287 'from: %s.', dest, source.host)
288 return not should_succeed
289 LOG.debug('ping result: %s', result)
Assaf Muller92fdc782018-05-31 10:32:47 -0400290
291 if validators.validate_ip_address(dest) is None:
292 # Assert that the return traffic was from the correct
293 # source address.
294 from_source = 'from %s' % dest
295 self.assertIn(from_source, result)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900296 return should_succeed
297
Assaf Muller92fdc782018-05-31 10:32:47 -0400298 return test_utils.call_until_true(
299 ping_remote, timeout or CONF.validation.ping_timeout, 1)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900300
301 def check_remote_connectivity(self, source, dest, should_succeed=True,
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200302 nic=None, mtu=None, fragmentation=True,
LIU Yulong68ab2452019-05-18 10:19:49 +0800303 servers=None, timeout=None,
304 ping_count=CONF.validation.ping_count):
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200305 try:
306 self.assertTrue(self._check_remote_connectivity(
LIU Yulong68ab2452019-05-18 10:19:49 +0800307 source, dest, ping_count, should_succeed, nic, mtu,
308 fragmentation,
Assaf Muller92fdc782018-05-31 10:32:47 -0400309 timeout=timeout))
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200310 except lib_exc.SSHTimeout as ssh_e:
311 LOG.debug(ssh_e)
312 self._log_console_output(servers)
313 raise
314 except AssertionError:
315 self._log_console_output(servers)
316 raise
Chandan Kumarc125fd12017-11-15 19:41:01 +0530317
318 def ping_ip_address(self, ip_address, should_succeed=True,
319 ping_timeout=None, mtu=None):
320 # the code is taken from tempest/scenario/manager.py in tempest git
321 timeout = ping_timeout or CONF.validation.ping_timeout
322 cmd = ['ping', '-c1', '-w1']
323
324 if mtu:
325 cmd += [
326 # don't fragment
327 '-M', 'do',
328 # ping receives just the size of ICMP payload
329 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
330 ]
331 cmd.append(ip_address)
332
333 def ping():
334 proc = subprocess.Popen(cmd,
335 stdout=subprocess.PIPE,
336 stderr=subprocess.PIPE)
337 proc.communicate()
338
339 return (proc.returncode == 0) == should_succeed
340
341 caller = test_utils.find_test_caller()
342 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
343 ' expected result is %(should_succeed)s', {
344 'caller': caller, 'ip': ip_address, 'timeout': timeout,
345 'should_succeed':
346 'reachable' if should_succeed else 'unreachable'
347 })
348 result = test_utils.call_until_true(ping, timeout, 1)
Manjeet Singh Bhatia8bbf8992019-03-04 11:59:57 -0800349
350 # To make sure ping_ip_address called by test works
351 # as expected.
352 self.assertTrue(result)
353
Chandan Kumarc125fd12017-11-15 19:41:01 +0530354 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
355 'ping result is %(result)s', {
356 'caller': caller, 'ip': ip_address, 'timeout': timeout,
357 'result': 'expected' if result else 'unexpected'
358 })
359 return result
Federico Ressie7417b72018-05-30 05:50:58 +0200360
361 def wait_for_server_status(self, server, status, client=None, **kwargs):
362 """Waits for a server to reach a given status.
363
364 :param server: mapping having schema {'id': <server_id>}
365 :param status: string status to wait for (es: 'ACTIVE')
366 :param clien: servers client (self.os_primary.servers_client as
367 default value)
368 """
369
370 client = client or self.os_primary.servers_client
371 waiters.wait_for_server_status(client, server['id'], status, **kwargs)
372
373 def wait_for_server_active(self, server, client=None):
374 """Waits for a server to reach active status.
375
376 :param server: mapping having schema {'id': <server_id>}
377 :param clien: servers client (self.os_primary.servers_client as
378 default value)
379 """
380 self.wait_for_server_status(
381 server, constants.SERVER_STATUS_ACTIVE, client)
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000382
383 def check_servers_hostnames(self, servers, log_errors=True):
384 """Compare hostnames of given servers with their names."""
385 try:
386 for server in servers:
387 kwargs = {}
388 try:
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200389 kwargs['port'] = (
390 server['port_forwarding_tcp']['external_port'])
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000391 except KeyError:
392 pass
393 ssh_client = ssh.Client(
394 self.fip['floating_ip_address'],
395 CONF.validation.image_ssh_user,
396 pkey=self.keypair['private_key'],
397 **kwargs)
398 self.assertIn(server['name'],
399 ssh_client.exec_command('hostname'))
400 except lib_exc.SSHTimeout as ssh_e:
401 LOG.debug(ssh_e)
402 if log_errors:
403 self._log_console_output(servers)
404 raise
405 except AssertionError as assert_e:
406 LOG.debug(assert_e)
407 if log_errors:
408 self._log_console_output(servers)
409 raise
Slawek Kaplonskic4e963e2019-09-11 22:55:34 +0200410
411 def nc_listen(self, server, ssh_client, port, protocol, echo_msg):
412 """Create nc server listening on the given TCP/UDP port.
413
414 Listener is created always on remote host.
415 """
416 udp = ''
417 if protocol.lower() == neutron_lib_constants.PROTO_NAME_UDP:
418 udp = '-u'
419 cmd = "sudo nc %(udp)s -p %(port)s -lk -e echo %(msg)s &" % {
420 'udp': udp, 'port': port, 'msg': echo_msg}
421 try:
422 return ssh_client.exec_command(cmd)
423 except lib_exc.SSHTimeout as ssh_e:
424 LOG.debug(ssh_e)
425 self._log_console_output([server])
426 raise
427
428 def nc_client(self, ip_address, port, protocol):
429 """Check connectivity to TCP/UDP port at host via nc.
430
431 Client is always executed locally on host where tests are executed.
432 """
433 udp = ''
434 if protocol.lower() == neutron_lib_constants.PROTO_NAME_UDP:
435 udp = '-u'
436 cmd = 'echo "knock knock" | nc -w 1 %(udp)s %(host)s %(port)s' % {
437 'udp': udp, 'host': ip_address, 'port': port}
438 result = shell.execute_local_command(cmd)
439 self.assertEqual(0, result.exit_status)
440 return result.stdout