-
Notifications
You must be signed in to change notification settings - Fork 33
Pythonify shunt setup methods and integrate into tests #927
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
515012f
Replace port nums with vars
anurag6 f53c679
Account for VNI
anurag6 3184d82
Remove delay
anurag6 9672f46
Base commit for python tests
anurag6 2736f18
Integrate python commands into shunt test
anurag6 44223da
Review fixes
anurag6 a7907d3
Remove python tests
anurag6 155a9bf
Use client and server args
anurag6 c5b3428
Fix arg
anurag6 7656263
Add main
anurag6 e9bee4c
Fix shunt tests
anurag6 b5c56ad
Change array slicing
anurag6 e9135f1
Lint fixes
anurag6 fec86cd
Alignment lint
anurag6 9281e2d
More lint fixes
anurag6 5edb403
Add a TODO
anurag6 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| """Shunt module""" | ||
|
|
||
| pass |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| """Module to help run shell commands and retrieve output""" | ||
|
|
||
| from __future__ import absolute_import | ||
| import subprocess | ||
| import multiprocessing | ||
|
|
||
|
|
||
| class ShellCommandHelper(): | ||
| """Hosts methods to run shell commands and retrieve output""" | ||
|
|
||
| TEN_MIN_SEC = 10 * 60 | ||
|
|
||
| def __init__(self): | ||
| pass | ||
|
|
||
| def _run_process_command(self, command, capture=False): | ||
| """ | ||
| Args: | ||
| command: Takes an str line or list[str] to be run in shell | ||
| capture: Setting to True captures stdout, stderr and return code | ||
| returns: | ||
| Popen object | ||
| """ | ||
| command_list = command.split() if isinstance(command, str) else command | ||
| pipeout = subprocess.PIPE if capture else None | ||
| return subprocess.Popen(command_list, stdout=pipeout, stderr=pipeout) | ||
|
|
||
| def _reap_process_command(self, process): | ||
| """ | ||
| Args: | ||
| process: Popen object to reap | ||
| returns: | ||
| return code of command, stdout, stderr | ||
| """ | ||
| process.wait(timeout=self.TEN_MIN_SEC) | ||
| stdout, stderr = process.communicate() | ||
| strout = str(stdout, 'utf-8') if stdout else None | ||
| strerr = str(stderr, 'utf-8') if stderr else None | ||
| return process.returncode, strout, strerr | ||
|
|
||
| # pylint: disable=too-many-arguments | ||
| def run_cmd(self, cmd, arglist=None, strict=True, | ||
| capture=False, docker_container=None, detach=False): | ||
| """ | ||
| Args: | ||
| cmd: command to be executed | ||
| arglist: Additional arguments to add to command | ||
| strict: Raises exception if command execution fails | ||
| capture: Prints out and captures stdout and stderr output of command | ||
| docker_container: Set if command needs to be run within a docker container | ||
| returns: | ||
| return code of command, stdout, stderr | ||
| """ | ||
| command = ("docker exec %s " % docker_container) if docker_container else "" | ||
| command = command.split() + ([cmd] + arglist) if arglist else command + cmd | ||
| if detach: | ||
| self._run_process_command(command, capture=capture) | ||
| return None, None, None | ||
| retcode, out, err = self._reap_process_command( | ||
| self._run_process_command(command, capture=capture)) | ||
| if strict and retcode: | ||
| if capture: | ||
| print('stdout: \n%s' % out) | ||
| print('stderr: \n%s' % err) | ||
| raise Exception('Command execution failed: %s' % str(command)) | ||
| return retcode, out, err | ||
|
|
||
| def parallelize(self, target, target_args, batch_size=200): | ||
| """ | ||
| Parallelizes multiple runs of a target method with multiprocessing. | ||
| Args: | ||
| target_args: List of tuples which serve as args for target. | ||
| List size determines number of jobs | ||
| target: Target method | ||
| batch_size: Thread pool size | ||
| """ | ||
| jobs = [] | ||
| for arg_tuple in target_args: | ||
| process = multiprocessing.Process(target=target, args=arg_tuple) | ||
| jobs.append(process) | ||
|
|
||
| batch_start = 0 | ||
| while batch_start <= len(jobs): | ||
| batch_end = batch_start + batch_size | ||
| batch_jobs = jobs[batch_start:batch_end] | ||
|
|
||
| for job in batch_jobs: | ||
| job.start() | ||
|
|
||
| for job in batch_jobs: | ||
| job.join() | ||
| job.close() | ||
|
|
||
| batch_start = batch_end | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| """Module to build VxLAN tunnels over SSH tunnels using shell commands""" | ||
|
|
||
| from __future__ import absolute_import | ||
| from shunt.shell_command_helper import ShellCommandHelper | ||
|
|
||
|
|
||
| def build_ssh_tunnel(ssh_in_port, ssh_out_port, remote_host, reverse=False): | ||
| """ | ||
| Args: | ||
| ssh_in_port: Destination port for tunnel | ||
| ssh_out_port: Source port for tunnel | ||
| remote_host: Remote host IP | ||
| reverse: Boolean. Creates reverse forwarding tunnel if True. | ||
| Returns: None | ||
| """ | ||
| shellcmd = ShellCommandHelper() | ||
| direction = 'R' if reverse else 'L' | ||
| shellcmd.run_cmd("ssh -o StrictHostKeyChecking=no -%s %s:127.0.0.1:%s -N -f %s" | ||
| % (direction, ssh_out_port, ssh_in_port, remote_host)) | ||
|
|
||
|
|
||
| def build_bidirectional_ssh_tunnel(ssh_in_port, ssh_out_port, remote_host): | ||
| """ | ||
| Args: | ||
| ssh_in_port: Incoming traffice port | ||
| ssh_out_port: Outgoing traffic port | ||
| remote_host: Remote host IP | ||
| Returns: None | ||
| """ | ||
| build_ssh_tunnel(ssh_in_port, ssh_out_port, remote_host, reverse=False) | ||
| build_ssh_tunnel(ssh_in_port, ssh_out_port, remote_host, reverse=True) | ||
|
|
||
|
|
||
| def create_virtual_if(if_name, if_ip): | ||
| """ | ||
| Args: | ||
| if_name: interface name | ||
| if_ip: interface IP | ||
| Returns: None | ||
| """ | ||
| shellcmd = ShellCommandHelper() | ||
| shellcmd.run_cmd("sudo ip link add %s type dummy" % (if_name)) | ||
| shellcmd.run_cmd("sudo ip addr add %s/24 dev %s" % (if_ip, if_name)) | ||
| shellcmd.run_cmd("sudo ip link set %s up" % (if_name)) | ||
|
|
||
|
|
||
| def socat_tcp_to_udp(src_port, dst_port): | ||
| """ | ||
| Socat command to map TCP port to UDP port | ||
| Args: | ||
| src_port: Source TCP port | ||
| dst_port: Destination UDP port | ||
| Returns: None | ||
| """ | ||
| shellcmd = ShellCommandHelper() | ||
| shellcmd.run_cmd("socat -T15 tcp4-listen:%s,reuseaddr,fork udp:localhost:%s" | ||
| % (src_port, dst_port), detach=True) | ||
|
|
||
|
|
||
| def socat_udp_to_tcp(src_port, dst_port): | ||
| """ | ||
| Socat command to map UDP port to TCP port | ||
| Args: | ||
| src_port: Source UDP port | ||
| dst_port: Destincation TCP port | ||
| Returns: None | ||
| """ | ||
| shellcmd = ShellCommandHelper() | ||
| shellcmd.run_cmd("socat -T15 udp4-listen:%s,reuseaddr,fork tcp:localhost:%s" | ||
| % (src_port, dst_port), detach=True) | ||
|
|
||
|
|
||
| def iptables_udp_change_source(dst_ip, dst_port, src_ip_port): | ||
| """ | ||
| iptables command to change source IP/ports of UDP packets | ||
| Args: | ||
| dst_ip: Destination IP of packet to be changed | ||
| dst_port: Destination port of packet to be changed | ||
| src_ip_port: <source_ip_to_be_added>:<source port range> e.g. 192.168.1.2:38000-45000 | ||
| Returns: None | ||
| """ | ||
| shellcmd = ShellCommandHelper() | ||
| shellcmd.run_cmd( | ||
| "sudo iptables -t nat -A POSTROUTING -p udp -d %s --dport %s -j SNAT --to-source %s" | ||
| % (dst_ip, dst_port, src_ip_port)) | ||
|
|
||
|
|
||
| def iptables_udp_divert_iface_traffic(iface, dst_port, target_dst_port): | ||
| """ | ||
| iptables command to divert udp traffic egressing from an interface to a different port | ||
| Args: | ||
| iface: Interface packets are egressign from | ||
| dst_port: Destination port of packets to be diverted | ||
| target_dst_port: Destination port packets are diverted to | ||
| Returns: None | ||
| """ | ||
| shellcmd = ShellCommandHelper() | ||
| shellcmd.run_cmd( | ||
| "sudo iptables -t nat -A OUTPUT -o %s -p udp --dport %s -j REDIRECT --to-ports %s" | ||
| % (iface, dst_port, target_dst_port)) | ||
|
|
||
|
|
||
| def create_vxlan_tunnel(vni, remote_ip, vxlan_port, vtep_ip): | ||
| """ | ||
| Method to create a VxLAN tunnel and assign an IP to the VTEP | ||
| Args: | ||
| vni: VNI of tunnel to be created | ||
| remote_ip: Remote IP address for underlay network | ||
| vxlan_port: Remote destination port for VxLAN tunnel | ||
| Returns: None | ||
| """ | ||
| shellcmd = ShellCommandHelper() | ||
| shellcmd.run_cmd( | ||
| "sudo ip link add vxlan type vxlan id %s remote %s dstport %s srcport %s %s nolearning" | ||
| % (vni, remote_ip, vxlan_port, vxlan_port, vxlan_port)) | ||
| shellcmd.run_cmd("sudo ip addr add %s/24 dev vxlan" % (vtep_ip)) | ||
| shellcmd.run_cmd("sudo ip link set vxlan up") | ||
|
|
||
|
|
||
| def build_vxlan_ssh_conn(conn_params): | ||
| """ | ||
| Args: | ||
| conn_params: JSON. Format:{ # TODO: Turn into a proto for easy config exchange. | ||
| virt_if_name: Interface name of virtual interface for underlay n/w | ||
| virt_if_ip: IP for virtual interface | ||
| ssh_in_port: Port for incoming traffic | ||
| ssh_out_port: Port for outgoing traffic | ||
| remote_ip: Remote IP of underlay n/w | ||
| vni: VxLAN identifier | ||
| vxlan_if_ip: VxLAN interface IP | ||
| } | ||
| Returns: None | ||
| """ | ||
| VXLAN_PORT = '4789' | ||
| LOCAL_HOST = '127.0.0.1' | ||
| VXLAN_SOURCE_PORT_RANGE = "38000-45000" | ||
| INTERMEDIATE_PORT = "20000" | ||
| create_virtual_if(conn_params['virt_if_name'], conn_params['virt_if_ip']) | ||
| socat_tcp_to_udp(conn_params['ssh_in_port'], VXLAN_PORT) | ||
| source_ip_port = conn_params['remote_ip'] + ":" + VXLAN_SOURCE_PORT_RANGE | ||
| iptables_udp_change_source(LOCAL_HOST, VXLAN_PORT, source_ip_port) | ||
| iptables_udp_divert_iface_traffic(conn_params['virt_if_name'], VXLAN_PORT, INTERMEDIATE_PORT) | ||
| socat_udp_to_tcp(INTERMEDIATE_PORT, conn_params['ssh_out_port']) | ||
| create_vxlan_tunnel( | ||
| conn_params['vni'], conn_params['remote_ip'], VXLAN_PORT, conn_params['vxlan_if_ip']) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| """Script to build vxlan over ssh for testing across docker hosts""" | ||
|
|
||
|
|
||
| from __future__ import absolute_import | ||
| import sys | ||
| from shunt.vxlan_over_ssh import build_vxlan_ssh_conn, build_bidirectional_ssh_tunnel | ||
|
|
||
| CONN_PARAMS = { | ||
| 'virt_if_name': 'ep0', | ||
| 'ssh_in_port': '30001', | ||
| 'ssh_out_port': '30000', | ||
| 'vni': '0' | ||
| } | ||
|
|
||
| CONN_PARAMS_CLIENT = { | ||
| 'virt_if_ip': '192.168.21.2', | ||
| 'remote_ip': '192.168.21.1', | ||
| 'vxlan_if_ip': '192.168.1.2' | ||
| } | ||
| CONN_PARAMS_CLIENT.update(CONN_PARAMS) | ||
|
|
||
| CONN_PARAMS_SERVER = { | ||
| 'virt_if_ip': '192.168.21.1', | ||
| 'remote_ip': '192.168.21.2', | ||
| 'vxlan_if_ip': '192.168.1.1' | ||
| } | ||
| CONN_PARAMS_SERVER.update(CONN_PARAMS) | ||
|
|
||
|
|
||
| def main(): | ||
| """Build vxlan over ssh for client/server""" | ||
| if sys.argv[1] == 'client': | ||
| build_bidirectional_ssh_tunnel( | ||
| CONN_PARAMS['ssh_in_port'], CONN_PARAMS['ssh_out_port'], 'shunt_host_server_1') | ||
| build_vxlan_ssh_conn(CONN_PARAMS_CLIENT) | ||
| elif sys.argv[1] == 'server': | ||
| build_vxlan_ssh_conn(CONN_PARAMS_SERVER) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,21 @@ | ||
| version: '2' | ||
|
|
||
| services: | ||
| host1: | ||
| host_client: | ||
| build: | ||
| context: ../.. | ||
| dockerfile: ./testing/shunt/Dockerfile.shunthost | ||
| command: bash -c "./start_shunt_host 1" | ||
| depends_on: | ||
| - "host_server" | ||
| command: bash -c "./start_shunt_host client" | ||
| #command: bash -c "tail -f" | ||
| cap_add: | ||
| - ALL | ||
| host2: | ||
| host_server: | ||
| build: | ||
| context: ../.. | ||
| dockerfile: ./testing/shunt/Dockerfile.shunthost | ||
| command: bash -c "./start_shunt_host 2" | ||
| command: bash -c "./start_shunt_host server" | ||
| #command: bash -c "tail -f" | ||
| cap_add: | ||
| - ALL |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are the return values used anywhere? It looks like everywhere that calls run_cmd discard the return values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None so far, but it could be used for debugging/checking if the command went through. Bound to be useful in future dev work. It is derived from the ShellCommandHelper in Forch.