tinet 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f3759871bec017bb9c62083793496f041da97ea833be5ff9ad337c11fdc50854
4
+ data.tar.gz: 0d61c7aff75feef1ec886e10fdb81a60ba54674fc933d757c63800e2df0081f4
5
+ SHA512:
6
+ metadata.gz: 007d93174d1823b979983739553f7626d9826dba2727270aef246dcbc99d87d46ecb4a00bdf70eebdf7b86fe21fc8eea3429f5ff25399441ff788f58b4d23a78
7
+ data.tar.gz: 5748d97ecbe6812c4bca50dee08b1b0f915f461dfcca5df178e1ba3680000d1205f5ff8f27b0aa898c44043af288047f69b26debbeaae559c8c512652f824afa
@@ -0,0 +1,50 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+ *.bridgesupport
21
+ build-iPhoneOS/
22
+ build-iPhoneSimulator/
23
+
24
+ ## Specific to RubyMotion (use of CocoaPods):
25
+ #
26
+ # We recommend against adding the Pods directory to your .gitignore. However
27
+ # you should judge for yourself, the pros and cons are mentioned at:
28
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
29
+ #
30
+ # vendor/Pods/
31
+
32
+ ## Documentation cache and generated files:
33
+ /.yardoc/
34
+ /_yardoc/
35
+ /doc/
36
+ /rdoc/
37
+
38
+ ## Environment normalization:
39
+ /.bundle/
40
+ /vendor/bundle
41
+ /lib/bundler/man/
42
+
43
+ # for a library or gem, you might want to ignore these files since the code is
44
+ # intended to run in multiple environments; otherwise, check them in:
45
+ # Gemfile.lock
46
+ # .ruby-version
47
+ # .ruby-gemset
48
+
49
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
50
+ .rvmrc
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,11 @@
1
+ # Change Log
2
+
3
+ ## [Unreleased](https://github.com/koki-sato/tinet-rb/compare/v0.0.2...HEAD)
4
+
5
+ ## 0.0.2
6
+
7
+ - Fix bugs
8
+
9
+ ## 0.0.1
10
+
11
+ - Basic implementation
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in tinet.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Koki Sato
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,40 @@
1
+ # tinet-rb
2
+
3
+ Ruby implement of [slankdev/tinet](https://github.com/slankdev/tinet).
4
+
5
+ :warning: **This is now prototype version. And also it is incompatible with YAML config format in [slankdev/tinet](https://github.com/slankdev/tinet).**
6
+
7
+ ## Requirements
8
+
9
+ - Docker
10
+ - Open vSwitch
11
+
12
+ ## Install
13
+
14
+ ```
15
+ $ gem install tinet
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```
21
+ $ tinet [OPTIONS] COMMAND
22
+
23
+ Options:
24
+ -f, [--specfile=SPECFILE] # Specify specification YAML file (Default: ./spec.yml)
25
+ -d, [--dry-run] # Print the recipes that are needed to execute
26
+ -v, [--version] # Show the TINET version information
27
+
28
+ Commands:
29
+ tinet build [OPTIONS] # Build Docker images from the spec file
30
+ tinet conf [OPTIONS] # Execute commands in a running container
31
+ tinet down [OPTIONS] # Stop and remove containers
32
+ tinet exec [OPTIONS] NODE COMMAND # Execute a command in a running container
33
+ tinet help [COMMAND] # Describe available commands or one specific command
34
+ tinet init # Generate template spec file
35
+ tinet ps [OPTIONS] # List services
36
+ tinet pull [OPTIONS] # Pull service images
37
+ tinet restart [OPTIONS] # Down and Up running containers
38
+ tinet up [OPTIONS] # Create and start containers
39
+ tinet version # Show the TINET version information
40
+ ```
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "tinet"
4
+
5
+ Tinet::CLI.start(ARGV)
@@ -0,0 +1,8 @@
1
+ require "tinet/version"
2
+ require "tinet/setting"
3
+ require "tinet/cli"
4
+
5
+ module Tinet
6
+ class InvalidYAMLError < StandardError; end
7
+ class InvalidTypeError < StandardError; end
8
+ end
@@ -0,0 +1,119 @@
1
+ require "thor"
2
+ require "tinet/setting"
3
+ require "tinet/command/build"
4
+ require "tinet/command/conf"
5
+ require "tinet/command/down"
6
+ require "tinet/command/exec"
7
+ require "tinet/command/init"
8
+ require "tinet/command/ps"
9
+ require "tinet/command/pull"
10
+ require "tinet/command/up"
11
+
12
+ module Tinet
13
+ class CLI < Thor
14
+ map %w[--version -v] => :version
15
+
16
+ desc 'init', 'Generate template spec file'
17
+ option :version, aliases: '-v', type: :boolean, default: false, desc: 'Show the TINET version information'
18
+ def init
19
+ return version if options[:version]
20
+ Tinet::Command::Init.new.run
21
+ end
22
+
23
+ desc 'ps [OPTIONS]', 'List services'
24
+ option :all, aliases: '-a', type: :boolean, desc: 'Show all containers (default shows just running)'
25
+ option 'dry-run', aliases: '-d', type: :boolean, default: false, desc: 'Print the recipes that are needed to execute'
26
+ option :version, aliases: '-v', type: :boolean, default: false, desc: 'Show the TINET version information'
27
+ def ps
28
+ return version if options[:version]
29
+ Tinet::Command::Ps.new(options).run
30
+ end
31
+
32
+ desc 'up [OPTIONS]', 'Create and start containers'
33
+ option :specfile, aliases: '-f', type: :string, default: Tinet::DEFAULT_SPECFILE_PATH, desc: 'Specify specification YAML file'
34
+ option 'dry-run', aliases: '-d', type: :boolean, default: false, desc: 'Print the recipes that are needed to execute'
35
+ option :version, aliases: '-v', type: :boolean, default: false, desc: 'Show the TINET version information'
36
+ def up
37
+ return version if options[:version]
38
+ Tinet::Command::Up.new(options).run
39
+ Tinet::Command::Conf.new(options).run
40
+ end
41
+
42
+ desc 'down [OPTIONS]', 'Stop and remove containers'
43
+ option :specfile, aliases: '-f', type: :string, default: Tinet::DEFAULT_SPECFILE_PATH, desc: 'Specify specification YAML file'
44
+ option 'dry-run', aliases: '-d', type: :boolean, default: false, desc: 'Print the recipes that are needed to execute'
45
+ option :version, aliases: '-v', type: :boolean, default: false, desc: 'Show the TINET version information'
46
+ def down
47
+ return version if options[:version]
48
+ Tinet::Command::Down.new(options).run
49
+ end
50
+
51
+ desc 'pull [OPTIONS]', 'Pull service images'
52
+ option :specfile, aliases: '-f', type: :string, default: Tinet::DEFAULT_SPECFILE_PATH, desc: 'Specify specification YAML file'
53
+ option 'dry-run', aliases: '-d', type: :boolean, default: false, desc: 'Print the recipes that are needed to execute'
54
+ option :version, aliases: '-v', type: :boolean, default: false, desc: 'Show the TINET version information'
55
+ def pull
56
+ return version if options[:version]
57
+ Tinet::Command::Pull.new(options).run
58
+ end
59
+
60
+ desc 'exec [OPTIONS] NODE COMMAND', 'Execute a command in a running container'
61
+ option :specfile, aliases: '-f', type: :string, default: Tinet::DEFAULT_SPECFILE_PATH, desc: 'Specify specification YAML file'
62
+ option 'dry-run', aliases: '-d', type: :boolean, default: false, desc: 'Print the recipes that are needed to execute'
63
+ option :version, aliases: '-v', type: :boolean, default: false, desc: 'Show the TINET version information'
64
+ def exec(node, command)
65
+ return version if options[:version]
66
+ Tinet::Command::Exec.new(options).run(node, command)
67
+ end
68
+
69
+ desc 'build [OPTIONS]', 'Build Docker images from the spec file'
70
+ option :specfile, aliases: '-f', type: :string, default: Tinet::DEFAULT_SPECFILE_PATH, desc: 'Specify specification YAML file'
71
+ option 'dry-run', aliases: '-d', type: :boolean, default: false, desc: 'Print the recipes that are needed to execute'
72
+ option :version, aliases: '-v', type: :boolean, default: false, desc: 'Show the TINET version information'
73
+ def build
74
+ return version if options[:version]
75
+ Tinet::Command::Build.new(options).run
76
+ end
77
+
78
+ desc 'conf [OPTIONS]', 'Execute commands in a running container'
79
+ option :specfile, aliases: '-f', type: :string, default: Tinet::DEFAULT_SPECFILE_PATH, desc: 'Specify specification YAML file'
80
+ option 'dry-run', aliases: '-d', type: :boolean, default: false, desc: 'Print the recipes that are needed to execute'
81
+ option :version, aliases: '-v', type: :boolean, default: false, desc: 'Show the TINET version information'
82
+ def conf
83
+ return version if options[:version]
84
+ Tinet::Command::Conf.new(options).run
85
+ end
86
+
87
+ desc 'restart [OPTIONS]', 'Down and Up running containers'
88
+ option :specfile, aliases: '-f', type: :string, default: Tinet::DEFAULT_SPECFILE_PATH, desc: 'Specify specification YAML file'
89
+ option 'dry-run', aliases: '-d', type: :boolean, default: false, desc: 'Print the recipes that are needed to execute'
90
+ option :version, aliases: '-v', type: :boolean, default: false, desc: 'Show the TINET version information'
91
+ def restart
92
+ return version if options[:version]
93
+ Tinet::Command::Down.new(options).run
94
+ Tinet::Command::Up.new(options).run
95
+ end
96
+
97
+ desc 'version', 'Show the TINET version information'
98
+ def version
99
+ puts "TINET version: #{Tinet::VERSION}"
100
+ end
101
+
102
+ # @note Override {Thor#help}
103
+ def help(command = nil, subcommand = false)
104
+ if command.nil?
105
+ puts <<-USAGE
106
+ Usage:
107
+ tinet [OPTIONS] COMMAND
108
+
109
+ Options:
110
+ -f, [--specfile=SPECFILE] # Specify specification YAML file (Default: ./spec.yml)
111
+ -d, [--dry-run] # Print the recipes that are needed to execute
112
+ -v, [--version] # Show the TINET version information
113
+
114
+ USAGE
115
+ end
116
+ super
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,130 @@
1
+ require "tinet/data"
2
+ require "tinet/link"
3
+ require "tinet/shell"
4
+
5
+ module Tinet
6
+ module Command
7
+ class Base
8
+ include Shell
9
+
10
+ def initialize(options = {})
11
+ @options = options
12
+ check_docker_installed unless dry_run
13
+ check_ovs_vsctl_installed unless dry_run
14
+ end
15
+
16
+ protected
17
+
18
+ # @return [Logger]
19
+ def logger
20
+ Tinet.logger
21
+ end
22
+
23
+ # @return [String]
24
+ def specfile
25
+ @options.fetch(:specfile, '')
26
+ end
27
+
28
+ # @return [boolean]
29
+ def dry_run
30
+ @options.fetch('dry-run', false)
31
+ end
32
+
33
+ # @return [Tinet::Data, nil]
34
+ def data
35
+ return nil if specfile.nil? || specfile.empty?
36
+ @data ||= Tinet::Data.parse(specfile)
37
+ end
38
+
39
+ # @return [Array<Tinet::Node>, nil]
40
+ def nodes
41
+ data.nodes unless data.nil?
42
+ end
43
+
44
+ # @return [Array<Tinet::Switch>, nil]
45
+ def switches
46
+ data.switches unless data.nil?
47
+ end
48
+
49
+ # @return [Array<Tinet::Link>]
50
+ def links
51
+ return nil if data.nil?
52
+ @links ||= Tinet::Link.link(nodes, switches)
53
+ end
54
+
55
+ # @param name [String]
56
+ # @return [String]
57
+ def namespaced(name)
58
+ "#{Tinet.namespace}-#{name}"
59
+ end
60
+
61
+ # @note Override {Tinet::Shell#sh}
62
+ def sh(command, dry_run: dry_run(), print: true, continue: false)
63
+ super
64
+ end
65
+
66
+ def mount_docker_netns(container, netns)
67
+ if dry_run
68
+ logger.info "PID=`sudo docker inspect #{container} -f {{.State.Pid}}`"
69
+ pid = '$PID'
70
+ else
71
+ pid, * = sudo "docker inspect #{container} -f {{.State.Pid}}"
72
+ end
73
+ sudo 'mkdir -p /var/run/netns'
74
+ sudo "ln -s /proc/#{pid}/ns/net /var/run/netns/#{netns}"
75
+ end
76
+
77
+ def exec_pre_cmd
78
+ data.options[:pre_cmd].each { |cmd| sudo command }
79
+ end
80
+
81
+ def exec_pre_init
82
+ data.options[:pre_init].each { |cmd| sudo command }
83
+ end
84
+
85
+ def exec_post_init
86
+ data.options[:post_init].each { |cmd| sudo command }
87
+ end
88
+
89
+ def exec_pre_conf
90
+ data.options[:pre_conf].each { |cmd| sudo command }
91
+ end
92
+
93
+ def exec_post_conf
94
+ data.options[:post_conf].each { |cmd| sudo command }
95
+ end
96
+
97
+ def exec_pre_down
98
+ data.options[:pre_down].each { |cmd| sudo command }
99
+ end
100
+
101
+ def exec_post_down
102
+ data.options[:post_down].each { |cmd| sudo command }
103
+ end
104
+
105
+ private
106
+
107
+ # @return [boolean]
108
+ def command_exist?(command)
109
+ *, status = sh "which #{command}", print: false, continue: true
110
+ status.success?
111
+ end
112
+
113
+ def check_docker_installed
114
+ unless command_exist?('docker')
115
+ message = 'ERROR: Docker is not installed. TINET requires Docker.'
116
+ logger.error(message)
117
+ exit(1)
118
+ end
119
+ end
120
+
121
+ def check_ovs_vsctl_installed
122
+ unless command_exist?('ovs-vsctl')
123
+ message = 'ERROR: Open vSwitch is not installed. TINET requires Open vSwitch.'
124
+ logger.error(message)
125
+ exit(1)
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,11 @@
1
+ require "tinet/command/base"
2
+
3
+ module Tinet
4
+ module Command
5
+ class Build < Base
6
+ def run
7
+ logger.error 'TODO: Implement'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ require "tinet/command/base"
2
+
3
+ module Tinet
4
+ module Command
5
+ class Conf < Base
6
+ def run
7
+ exec_pre_cmd
8
+ exec_pre_conf
9
+
10
+ nodes.each do |node|
11
+ node.cmds.each do |cmd|
12
+ case node.type
13
+ when :docker
14
+ sudo "docker exec #{namespaced(node.name)} #{cmd} > /dev/null"
15
+ when :netns
16
+ sudo "ip netns exec #{namespaced(node.name)} #{cmd} > /dev/null"
17
+ end
18
+ end
19
+ end
20
+
21
+ exec_post_conf
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,43 @@
1
+ require "tinet/command/base"
2
+
3
+ module Tinet
4
+ module Command
5
+ class Down < Base
6
+ def run
7
+ exec_pre_cmd
8
+ exec_pre_down
9
+
10
+ nodes.each do |node|
11
+ node.interfaces.each do |interface|
12
+ if interface.type == :phys
13
+ detach_physnet_from_docker("#{namespaced(node.name)}", interface.name)
14
+ end
15
+ end
16
+ end
17
+
18
+ nodes.each do |node|
19
+ case node.type
20
+ when :docker
21
+ sudo "docker stop #{namespaced(node.name)}"
22
+ when :netns
23
+ sudo "ip netns del #{namespaced(node.name)}"
24
+ end
25
+ end
26
+
27
+ switches.each do |switch|
28
+ sudo "ovs-vsctl del-br #{namespaced(switch.name)}"
29
+ end
30
+
31
+ exec_post_down
32
+ end
33
+
34
+ private
35
+
36
+ def detach_physnet_from_docker(container, ifname)
37
+ mount_docker_netns(container, container)
38
+ sudo "ip netns exec #{container} ip link set #{ifname} netns 1"
39
+ sudo "ip netns del #{container}"
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,18 @@
1
+ require "tinet/command/base"
2
+
3
+ module Tinet
4
+ module Command
5
+ class Exec < Base
6
+ def run(node_name, command)
7
+ node = nodes.find { |node| node.name == node_name }
8
+ raise "No such container: #{node_name}" if node.nil?
9
+ case node.type
10
+ when :docker
11
+ sudo "docker exec -it #{namespaced(node.name)} #{command}"
12
+ when :netns
13
+ sudo "ip netns exec #{namespaced(node.name)} #{command}"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ require "fileutils"
2
+ require "tinet/setting"
3
+ require "tinet/shell"
4
+ require "tinet/command/base"
5
+
6
+ module Tinet
7
+ module Command
8
+ class Init < Base
9
+ def run
10
+ template = File.join(Tinet::ROOT, 'spec.template.yml')
11
+ specfile = Tinet::DEFAULT_SPECFILE_PATH
12
+ FileUtils.cp(template, specfile)
13
+ logger.info 'Initialized. Check spec.yml'
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ require "tinet/command/base"
2
+
3
+ module Tinet
4
+ module Command
5
+ class Ps < Base
6
+ def run
7
+ if all
8
+ stdout, * = sudo "docker ps -a -f name=#{Tinet.namespace}"
9
+ else
10
+ stdout, * = sudo "docker ps -f name=#{Tinet.namespace}"
11
+ end
12
+
13
+ unless dry_run
14
+ logger.info 'TINET Docker Containers'
15
+ logger.info '-' * 50
16
+ logger.info stdout
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ # @return [boolean]
23
+ def all
24
+ @options[:all]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,16 @@
1
+ require "tinet/command/base"
2
+
3
+ module Tinet
4
+ module Command
5
+ class Pull < Base
6
+ def run
7
+ nodes.each do |node|
8
+ unless node.image.nil?
9
+ stdout, * = sudo "docker pull #{node.image}"
10
+ logger.info stdout unless dry_run
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,104 @@
1
+ require "tinet/command/base"
2
+
3
+ module Tinet
4
+ module Command
5
+ class Up < Base
6
+ def run
7
+ exec_pre_cmd
8
+ exec_pre_init
9
+
10
+ # Create Nodes and Switches
11
+ nodes.each { |node| node_up(node) }
12
+ switches.each { |switch| switch_up(switch) }
13
+
14
+ # Create Links
15
+ links.each { |link| link_up(link) }
16
+
17
+ # Attach physnet / veth to Conitaner
18
+ nodes.each do |node|
19
+ node.interfaces.each do |interface|
20
+ case interface.type
21
+ when :phys
22
+ koko_physnet("#{namespaced(node.name)}", interface.name)
23
+ when :veth
24
+ sudo "ip link add #{interface.name} type veth peer name #{interface.args}"
25
+ koko_physnet("#{namespaced(node.name)}", interface.name)
26
+ sudo "ip link set #{interface.args} up"
27
+ end
28
+ end
29
+ end
30
+
31
+ exec_post_init
32
+ end
33
+
34
+ private
35
+
36
+ def all
37
+ @options[:all]
38
+ end
39
+
40
+ # @param node [Tinet::Node]
41
+ def node_up(node)
42
+ case node.type
43
+ when :docker
44
+ sudo "docker run -td --hostname #{node.name} --net=none --name #{namespaced(node.name)} --rm --privileged #{node.image}"
45
+ when :netns
46
+ sudo "ip netns add #{namespaced(node.name)}"
47
+ end
48
+ end
49
+
50
+ # @param switch [Tinet::Switch]
51
+ def switch_up(switch)
52
+ sudo "ovs-vsctl add-br #{namespaced(switch.name)}"
53
+ sudo "ip link set #{namespaced(switch.name)} up"
54
+ end
55
+
56
+ # @param link [Tinet::Link]
57
+ def link_up(link)
58
+ left, right = link.left, link.right
59
+ case link.type
60
+ when :n2n
61
+ mount_docker_netns(namespaced(left.node.name), namespaced(left.node.name)) if left.node.type == :docker
62
+ mount_docker_netns(namespaced(right.node.name), namespaced(right.node.name)) if right.node.type == :docker
63
+ sudo "ip link add #{left.name} netns #{namespaced(left.node.name)} type veth peer name #{right.name} netns #{namespaced(right.node.name)}"
64
+ sudo "ip netns exec #{namespaced(left.node.name)} ip link set #{left.name} up"
65
+ sudo "ip netns exec #{namespaced(right.node.name)} ip link set #{right.name} up"
66
+ sudo "ip netns del #{namespaced(left.node.name)}" if left.node.type == :docker
67
+ sudo "ip netns del #{namespaced(right.node.name)}" if right.node.type == :docker
68
+ when :s2n
69
+ case right.node.type
70
+ when :docker
71
+ kokobr(namespaced(left.switch.name), namespaced(right.node.name), right.name)
72
+ when :netns
73
+ kokobr_netns(namespaced(left.switch.name), namespaced(right.node.name), right.name)
74
+ end
75
+ end
76
+ end
77
+
78
+ def kokobr(bridge, container, ifname)
79
+ mount_docker_netns(container, container)
80
+ sudo "ip link add name #{ifname} type veth peer name #{container}-#{ifname}"
81
+ sudo "ip link set dev #{ifname} netns #{container}"
82
+ sudo "ip link set #{container}-#{ifname} up"
83
+ sudo "ip netns exec #{container} ip link set #{ifname} up"
84
+ sudo "ip netns del #{container}"
85
+ sudo "ovs-vsctl add-port #{bridge} #{container}-#{ifname}"
86
+ end
87
+
88
+ def kokobr_netns(bridge, container, ifname)
89
+ sudo "ip link add name #{ifname} type veth peer name #{container}-#{ifname}"
90
+ sudo "ip link set dev #{ifname} netns #{container}"
91
+ sudo "ip link set #{container}-#{ifname} up"
92
+ sudo "ip netns exec #{container} ip link set #{ifname} up"
93
+ sudo "ovs-vsctl add-port #{bridge} #{container}-#{ifname}"
94
+ end
95
+
96
+ def koko_physnet(container, netif)
97
+ mount_docker_netns(container, container)
98
+ sudo "ip link set dev #{netif} netns #{container}"
99
+ sudo "ip netns exec #{container} ip link set #{netif} up"
100
+ sudo "ip netns del #{container}"
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,54 @@
1
+ require "yaml"
2
+ require "tinet/node"
3
+ require "tinet/switch"
4
+
5
+ module Tinet
6
+ class Data
7
+ class << self
8
+ # @param yaml_path [String]
9
+ # @return [Tinet::Data]
10
+ def parse(yaml_path)
11
+ yaml = YAML.load_file(yaml_path)
12
+ raise InvalidYAMLError, "Nodes must be array" unless yaml['nodes'].is_a?(Array)
13
+ raise InvalidYAMLError, "Switches must be array" unless yaml['switches'].is_a?(Array)
14
+
15
+ namespace = yaml.fetch('meta', {}).fetch('namespace', nil)
16
+ Tinet.namespace = namespace unless namespace.nil? || namespace.empty?
17
+
18
+ nodes = yaml['nodes'].map { |node| Tinet::Node.parse(node) }
19
+ switches = yaml['switches'].map { |switch| Tinet::Switch.parse(switch) }
20
+ options = {
21
+ pre_cmd: fetch(yaml, 'pre_cmd'),
22
+ pre_init: fetch(yaml, 'pre_init'),
23
+ post_init: fetch(yaml, 'post_init'),
24
+ pre_conf: fetch(yaml, 'pre_conf'),
25
+ post_conf: fetch(yaml, 'post_conf'),
26
+ pre_down: fetch(yaml, 'pre_down'),
27
+ post_down: fetch(yaml, 'post_down')
28
+ }
29
+
30
+ self.new(nodes, switches, options)
31
+ end
32
+
33
+ private
34
+
35
+ # @param yaml [Hash]
36
+ # @param key [String]
37
+ # @return [Array<String>]
38
+ def fetch(yaml, key)
39
+ yaml.fetch(key, {}).fetch('cmds', []).map { |cmd| cmd['cmd'] || cmd }
40
+ end
41
+ end
42
+
43
+ attr_reader :nodes, :switches, :options
44
+
45
+ # @param nodes [Array<Tinet::Node>]
46
+ # @param switches [Array<Tinet::Switch>]
47
+ # @param options [Hash{Symbol => Array<String>}]
48
+ def initialize(nodes, switches, options)
49
+ @nodes = nodes
50
+ @switches = switches
51
+ @options = options
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,65 @@
1
+ module Tinet
2
+ class Link
3
+ TYPES = %w(n2n s2n).freeze
4
+
5
+ class << self
6
+ # @param nodes [Array<Tinet::Node>]
7
+ # @param switches [Array<Tinet::Switch>]
8
+ # @return [Array<Tinet::Link>]
9
+ def link(nodes, switches)
10
+ link_n2n(nodes) + link_s2n(nodes, switches)
11
+ end
12
+
13
+ # @param nodes [Array<Tinet::Node>]
14
+ # @return [Array<Tinet::Link>]
15
+ def link_n2n(nodes)
16
+ list, index = [], {}
17
+ nodes.each do |node|
18
+ node.interfaces.each do |interface|
19
+ if interface.type == :direct
20
+ key = "#{interface.args}-#{node.name}##{interface.name}" # example: C0#net0-C1#net0
21
+ if index.key?(key)
22
+ list << self.new(index[key], interface, :n2n)
23
+ else
24
+ key = "#{node.name}##{interface.name}-#{interface.args}" # example: C1#net0-C0#net0
25
+ index[key] = interface
26
+ end
27
+ end
28
+ end
29
+ end
30
+ list
31
+ end
32
+
33
+ # @param nodes [Array<Tinet::Node>]
34
+ # @param switches [Array<Tinet::Switch>]
35
+ # @return [Array<Tinet::Link>]
36
+ def link_s2n(nodes, switches)
37
+ list, index = [], {}
38
+ nodes.each do |node|
39
+ node.interfaces.each do |interface|
40
+ key = "#{node.name}-#{interface.args}" # example: C0-B0
41
+ index[key] = interface if interface.type == :bridge
42
+ end
43
+ end
44
+ switches.each do |switch|
45
+ switch.interfaces.each do |interface|
46
+ key = "#{interface.args}-#{switch.name}" # example: C0-B0
47
+ list << self.new(interface, index[key], :s2n) if index.key?(key)
48
+ end
49
+ end
50
+ list
51
+ end
52
+ end
53
+
54
+ attr_reader :left, :right, :type
55
+
56
+ # @param left [Tinet::Node::Interfase, Tinet::Switch::Interfase]
57
+ # @param right [Tinet::Node::Interfase]
58
+ # @param type [Symbol]
59
+ def initialize(left, right, type)
60
+ @left = left
61
+ @right = right
62
+ @type = type
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,65 @@
1
+ module Tinet
2
+ class Node
3
+ TYPES = %w(docker netns).freeze
4
+
5
+ # @param node_hash [Hash]
6
+ # @return [Tinet::Node]
7
+ def self.parse(node_hash)
8
+ name, type, interfaces = node_hash['name'], node_hash['type'], node_hash['interfaces']
9
+ raise InvalidYAMLError, "Node name is missing" if name.nil? || name.empty?
10
+ raise InvalidTypeError, "Unknown node type: #{type}" unless TYPES.include?(type)
11
+ raise InvalidYAMLError, "Node interfaces must be array" unless interfaces.is_a?(Array)
12
+
13
+ interfaces = interfaces.map { |interface| Interfase.parse(interface) }
14
+ cmds = node_hash.fetch('cmds', []).map { |cmd| cmd['cmd'] || cmd }
15
+ self.new(name, type.to_sym, node_hash['image'], node_hash['build'], interfaces, cmds)
16
+ end
17
+
18
+ attr_reader :name, :type, :image, :build, :interfaces, :cmds
19
+
20
+ # @param name [String]
21
+ # @param type [Symbol]
22
+ # @param image [String, nil]
23
+ # @param build [String, nil]
24
+ # @param interfaces [Array<Tinet::Node::Interfase>]
25
+ # @param cmds [Array<String>]
26
+ def initialize(name, type, image, build, interfaces, cmds)
27
+ @name = name
28
+ @type = type
29
+ @image = image
30
+ @build = build
31
+ @interfaces = interfaces
32
+ @cmds = cmds
33
+ interfaces.each { |interface| interface.node = self }
34
+ end
35
+
36
+ class Interfase
37
+ TYPES = %w(direct bridge veth phys).freeze
38
+
39
+ # @param interface_hash [Hash]
40
+ # @return [Tinet::Node::Interfase]
41
+ def self.parse(interface_hash)
42
+ name, type, args = interface_hash['name'], interface_hash['type'], interface_hash['args']
43
+ raise InvalidYAMLError, "Interfase name is missing" if name.nil? || name.empty?
44
+ raise InvalidTypeError, "Unknown interface type: #{type}" unless TYPES.include?(type)
45
+ self.new(name, type.to_sym, args)
46
+ end
47
+
48
+ attr_reader :name, :type, :args, :node
49
+
50
+ # @param name [String]
51
+ # @param type [Symbol]
52
+ # @param args [String, nil]
53
+ def initialize(name, type, args)
54
+ @name = name
55
+ @type = type.to_sym
56
+ @args = args
57
+ @node = nil
58
+ end
59
+
60
+ def node=(node)
61
+ @node = node
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,54 @@
1
+ require "logger"
2
+
3
+ module Tinet
4
+ class Setting
5
+ ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
6
+ DEFAULT_SPECFILE_PATH = './spec.yml'.freeze
7
+
8
+ # @return [String]
9
+ def root
10
+ ROOT
11
+ end
12
+
13
+ # @return [String]
14
+ def namespace
15
+ @namespace ||= 'tinet'
16
+ end
17
+
18
+ # @param namespace [String]
19
+ # @return [String]
20
+ def namespace=(namespace)
21
+ @namespace = namespace
22
+ end
23
+
24
+ # @return [Logger]
25
+ def logger
26
+ @logger ||= Logger.new($stderr).tap do |logger|
27
+ logger.formatter = proc { |_sev, _dtm, _name, message| message + "\n" }
28
+ logger.level = Logger::INFO
29
+ end
30
+ end
31
+
32
+ # @param logger [Logger]
33
+ # @return [Logger]
34
+ def logger=(logger)
35
+ @logger = logger
36
+ end
37
+ end
38
+
39
+ SettingSingleton = Setting.new
40
+
41
+ class << self
42
+ def const_missing(name)
43
+ Setting.const_get name
44
+ rescue NameError
45
+ super
46
+ end
47
+
48
+ def method_missing(method, *args)
49
+ SettingSingleton.__send__ method, *args
50
+ rescue NoMethodError
51
+ super
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,36 @@
1
+ require "open3"
2
+ require "tinet/setting"
3
+
4
+ module Tinet
5
+ module Shell
6
+ DummyStatus = Struct.new(:success) do |status|
7
+ def success?
8
+ success
9
+ end
10
+ end
11
+
12
+ def sudo(command)
13
+ sh "sudo #{command}"
14
+ end
15
+
16
+ def sh(command, dry_run: false, print: false, continue: false)
17
+ if dry_run || print
18
+ Tinet.logger.info command
19
+ else
20
+ Tinet.logger.debug command
21
+ end
22
+
23
+ return ['', '', DummyStatus.new(true)] if dry_run
24
+
25
+ stdout, stderr, status = Open3.capture3(command)
26
+
27
+ if !status.success? && !continue
28
+ Tinet.logger.error "Command '#{command}' failed:"
29
+ Tinet.logger.error " #{stderr.chomp}" unless stderr.chomp.empty?
30
+ exit(status.to_i)
31
+ end
32
+
33
+ [stdout.chomp, stderr.chomp, status]
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,53 @@
1
+ module Tinet
2
+ class Switch
3
+ # @param switch_hash [Hash]
4
+ # @return [Tinet::Switch]
5
+ def self.parse(switch_hash)
6
+ name, interfaces = switch_hash['name'], switch_hash['interfaces']
7
+ raise InvalidYAMLError, "Switch name is missing" if name.nil? || name.empty?
8
+ raise InvalidYAMLError, "Switch interfaces must be array" unless interfaces.is_a?(Array)
9
+
10
+ interfaces = interfaces.map { |interface| Interfase.parse(interface) }
11
+ self.new(name, interfaces)
12
+ end
13
+
14
+ attr_reader :name, :interfaces
15
+
16
+ # @param name [String]
17
+ # @param interfaces [Array<Tinet::Switch::Interfase>]
18
+ def initialize(name, interfaces)
19
+ @name = name
20
+ @interfaces = interfaces
21
+ interfaces.each { |interface| interface.switch = self }
22
+ end
23
+
24
+ class Interfase
25
+ TYPES = %w(docker netns phys).freeze
26
+
27
+ # @param interface_hash [Hash]
28
+ # @return [Tinet::Switch::Interfase]
29
+ def self.parse(interface_hash)
30
+ name, type, args = interface_hash['name'], interface_hash['type'], interface_hash['args']
31
+ raise InvalidYAMLError, "Interfase name is missing" if name.nil? || name.empty?
32
+ raise InvalidTypeError, "Unknown interface type: #{type}" unless TYPES.include?(type)
33
+ self.new(name, type.to_sym, args)
34
+ end
35
+
36
+ attr_reader :name, :type, :args, :switch
37
+
38
+ # @param name [String]
39
+ # @param type [Symbol]
40
+ # @param args [String, nil]
41
+ def initialize(name, type, args)
42
+ @name = name
43
+ @type = type.to_sym
44
+ @args = args
45
+ @switch = nil
46
+ end
47
+
48
+ def switch=(switch)
49
+ @switch = switch
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ module Tinet
2
+ VERSION = "0.0.2".freeze
3
+ end
@@ -0,0 +1,39 @@
1
+ nodes:
2
+ - name: C0
3
+ type: docker # optional (default: docker)
4
+ image: slankdev/ubuntu:18.04
5
+ interfaces:
6
+ - { name: net0, type: direct, args: C1#net0 }
7
+ - { name: net1, type: bridge, args: B0 }
8
+ - { name: net2, type: veth , args: peer0 }
9
+ - { name: net3, type: phys }
10
+ cmds:
11
+ - cmd: ip link set dev net0 up
12
+
13
+ - name: C1
14
+ type: netns # optional (default: docker)
15
+ interfaces:
16
+ - { name: net0, type: direct, args: C0#net0 }
17
+ - { name: net1, type: bridge, args: B0 }
18
+ cmds:
19
+ - cmd: echo slankdev slankdev
20
+ - cmd: >-
21
+ echo slankdev &&
22
+ echo slnakdev
23
+
24
+ switches:
25
+ - name: B0
26
+ interfaces:
27
+ - { name: net0, type: docker, args: C0 }
28
+ - { name: net0, type: netns, args: C1 }
29
+
30
+ test:
31
+ - name: p2p
32
+ cmds:
33
+ - cmd: docker exec C0 ping -c2 10.0.0.2
34
+ - cmd: echo slankdev slankdev
35
+
36
+ - name: lo
37
+ cmds:
38
+ - cmd: docker exec C0 ping -c2 10.255.0.1
39
+ - cmd: echo slankdev slankdev
@@ -0,0 +1,32 @@
1
+ lib = File.expand_path("lib", __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "tinet/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "tinet"
7
+ spec.version = Tinet::VERSION
8
+ spec.authors = ["koki-sato"]
9
+ spec.email = ["admin@koki-sato.com"]
10
+
11
+ spec.summary = "Ruby implement of slankdev/tinet."
12
+ spec.homepage = "https://github.com/koki-sato/tinet-rb"
13
+ spec.license = "MIT"
14
+
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = "https://github.com/koki-sato/tinet-rb"
17
+ spec.metadata["changelog_uri"] = "https://github.com/koki-sato/tinet-rb/blob/master/CHANGELOG.md"
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|sample|features)/}) }
23
+ end
24
+ spec.bindir = "bin"
25
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
26
+ spec.require_paths = ["lib"]
27
+
28
+ spec.add_dependency "thor"
29
+ spec.add_development_dependency "bundler", "~> 2.0"
30
+ spec.add_development_dependency "rake", "~> 10.0"
31
+ spec.add_development_dependency "rspec", "~> 3.0"
32
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tinet
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - koki-sato
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-10-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ description:
70
+ email:
71
+ - admin@koki-sato.com
72
+ executables:
73
+ - tinet
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - CHANGELOG.md
80
+ - Gemfile
81
+ - LICENSE
82
+ - README.md
83
+ - Rakefile
84
+ - bin/tinet
85
+ - lib/tinet.rb
86
+ - lib/tinet/cli.rb
87
+ - lib/tinet/command/base.rb
88
+ - lib/tinet/command/build.rb
89
+ - lib/tinet/command/conf.rb
90
+ - lib/tinet/command/down.rb
91
+ - lib/tinet/command/exec.rb
92
+ - lib/tinet/command/init.rb
93
+ - lib/tinet/command/ps.rb
94
+ - lib/tinet/command/pull.rb
95
+ - lib/tinet/command/up.rb
96
+ - lib/tinet/data.rb
97
+ - lib/tinet/link.rb
98
+ - lib/tinet/node.rb
99
+ - lib/tinet/setting.rb
100
+ - lib/tinet/shell.rb
101
+ - lib/tinet/switch.rb
102
+ - lib/tinet/version.rb
103
+ - spec.template.yml
104
+ - tinet.gemspec
105
+ homepage: https://github.com/koki-sato/tinet-rb
106
+ licenses:
107
+ - MIT
108
+ metadata:
109
+ homepage_uri: https://github.com/koki-sato/tinet-rb
110
+ source_code_uri: https://github.com/koki-sato/tinet-rb
111
+ changelog_uri: https://github.com/koki-sato/tinet-rb/blob/master/CHANGELOG.md
112
+ post_install_message:
113
+ rdoc_options: []
114
+ require_paths:
115
+ - lib
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ requirements: []
127
+ rubyforge_project:
128
+ rubygems_version: 2.7.6
129
+ signing_key:
130
+ specification_version: 4
131
+ summary: Ruby implement of slankdev/tinet.
132
+ test_files: []