pio 0.10.1 → 0.11.0

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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/README.md +33 -2
  4. data/examples/echo_read.rb +1 -1
  5. data/examples/features_read.rb +1 -1
  6. data/examples/flow_mod_new.rb +13 -0
  7. data/examples/flow_mod_read.rb +6 -0
  8. data/features/echo_read.feature +27 -3
  9. data/features/features_read.feature +46 -2
  10. data/features/flow_mod_read.feature +186 -0
  11. data/features/hello_read.feature +9 -0
  12. data/features/packet_data/flow_mod_add.raw +0 -0
  13. data/features/packet_data/flow_mod_delete.raw +0 -0
  14. data/features/packet_data/flow_mod_delete_strict.raw +0 -0
  15. data/features/packet_data/flow_mod_modify.raw +0 -0
  16. data/features/packet_data/flow_mod_modify_strict.raw +0 -0
  17. data/features/packet_in_read.feature +13 -0
  18. data/features/packet_out_read.feature +16 -0
  19. data/features/step_definitions/packet_data_steps.rb +10 -1
  20. data/lib/pio.rb +1 -0
  21. data/lib/pio/echo.rb +10 -8
  22. data/lib/pio/enqueue.rb +1 -1
  23. data/lib/pio/features.rb +64 -7
  24. data/lib/pio/flow_mod.rb +86 -0
  25. data/lib/pio/hello.rb +4 -74
  26. data/lib/pio/ipv4_address.rb +1 -1
  27. data/lib/pio/match.rb +167 -0
  28. data/lib/pio/open_flow.rb +1 -0
  29. data/lib/pio/open_flow/actions.rb +65 -0
  30. data/lib/pio/open_flow/flags.rb +12 -9
  31. data/lib/pio/open_flow/message.rb +105 -21
  32. data/lib/pio/open_flow/phy_port.rb +31 -27
  33. data/lib/pio/open_flow/type.rb +1 -0
  34. data/lib/pio/packet_in.rb +2 -12
  35. data/lib/pio/packet_out.rb +4 -74
  36. data/lib/pio/send_out_port.rb +1 -0
  37. data/lib/pio/type/ip_address.rb +2 -7
  38. data/lib/pio/version.rb +1 -1
  39. data/pio.gemspec +8 -8
  40. data/spec/pio/echo/reply_spec.rb +61 -6
  41. data/spec/pio/echo/request_spec.rb +61 -6
  42. data/spec/pio/features/reply_spec.rb +81 -4
  43. data/spec/pio/features/request_spec.rb +88 -41
  44. data/spec/pio/flow_mod_spec.rb +151 -0
  45. data/spec/pio/hello_spec.rb +73 -56
  46. data/spec/pio/match_spec.rb +194 -0
  47. data/spec/pio/packet_in_spec.rb +35 -38
  48. data/spec/pio/packet_out_spec.rb +243 -182
  49. data/spec/pio/wildcards_spec.rb +115 -0
  50. metadata +37 -30
  51. data/features/packet_data/echo.raw +0 -0
  52. data/lib/pio/echo/message.rb +0 -49
  53. data/lib/pio/echo/reply.rb +0 -41
  54. data/lib/pio/echo/request.rb +0 -43
  55. data/lib/pio/features/reply.rb +0 -88
  56. data/lib/pio/features/request.rb +0 -68
  57. data/lib/pio/open_flow/parser.rb +0 -22
  58. data/spec/pio/echo_spec.rb +0 -49
  59. data/spec/pio/features_spec.rb +0 -96
data/lib/pio/open_flow.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'pio/open_flow/actions'
1
2
  require 'pio/open_flow/flags'
2
3
  require 'pio/open_flow/message'
3
4
  require 'pio/open_flow/open_flow_header'
@@ -0,0 +1,65 @@
1
+ require 'bindata'
2
+ require 'pio/enqueue'
3
+ require 'pio/send_out_port'
4
+ require 'pio/set_eth_addr'
5
+ require 'pio/set_ip_addr'
6
+ require 'pio/set_ip_tos'
7
+ require 'pio/set_transport_port'
8
+ require 'pio/set_vlan_priority'
9
+ require 'pio/set_vlan_vid'
10
+ require 'pio/strip_vlan_header'
11
+
12
+ module Pio
13
+ module OpenFlow
14
+ # Actions list.
15
+ class Actions < BinData::Primitive
16
+ ACTION_CLASS = {
17
+ 0 => Pio::SendOutPort,
18
+ 1 => Pio::SetVlanVid,
19
+ 2 => Pio::SetVlanPriority,
20
+ 3 => Pio::StripVlanHeader,
21
+ 4 => Pio::SetEthSrcAddr,
22
+ 5 => Pio::SetEthDstAddr,
23
+ 6 => Pio::SetIpSrcAddr,
24
+ 7 => Pio::SetIpDstAddr,
25
+ 8 => Pio::SetIpTos,
26
+ 9 => Pio::SetTransportSrcPort,
27
+ 10 => Pio::SetTransportDstPort,
28
+ 11 => Pio::Enqueue
29
+ }
30
+
31
+ mandatory_parameter :length
32
+
33
+ endian :big
34
+
35
+ string :binary, read_length: :length
36
+
37
+ def set(value)
38
+ self.binary = [value].flatten.map(&:to_binary).join
39
+ end
40
+
41
+ # rubocop:disable MethodLength
42
+ # This method smells of :reek:TooManyStatements
43
+ def get
44
+ actions = []
45
+ tmp = binary
46
+ while tmp.length > 0
47
+ type = BinData::Uint16be.read(tmp)
48
+ begin
49
+ action = ACTION_CLASS.fetch(type).read(tmp)
50
+ tmp = tmp[action.message_length..-1]
51
+ actions << action
52
+ rescue KeyError
53
+ raise "action type #{type} is not supported."
54
+ end
55
+ end
56
+ actions
57
+ end
58
+ # rubocop:enable MethodLength
59
+
60
+ def [](index)
61
+ get[index]
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,8 +1,17 @@
1
1
  # bitmap functions.
2
+ # This class smells of :reek:DataClump
2
3
  module Flags
4
+ def flags_32bit(name, flags)
5
+ _def_flags name, 32, flags
6
+ end
7
+
8
+ def flags_16bit(name, flags)
9
+ _def_flags name, 16, flags
10
+ end
11
+
3
12
  # rubocop:disable MethodLength
4
13
  # This method smells of :reek:TooManyStatements
5
- def def_flags(name, flags)
14
+ def _def_flags(name, size, flags)
6
15
  flag_value = case flags
7
16
  when Array
8
17
  shift = 0
@@ -14,20 +23,15 @@ module Flags
14
23
  when Hash
15
24
  flags
16
25
  end
17
- _def_class name, flag_value
18
- end
19
- # rubocop:enable MethodLength
20
26
 
21
- # rubocop:disable MethodLength
22
- def _def_class(name, flags)
23
27
  klass_name = name.to_s.split('_').map(&:capitalize).join
24
- flags_hash = flags.inspect
28
+ flags_hash = flag_value.inspect
25
29
 
26
30
  code = %{
27
31
  class #{klass_name} < BinData::Primitive
28
32
  endian :big
29
33
 
30
- uint32 :#{name}
34
+ uint#{size} :#{name}
31
35
 
32
36
  def get
33
37
  list = #{flags_hash}
@@ -48,5 +52,4 @@ module Flags
48
52
  }
49
53
  module_eval code
50
54
  end
51
- # rubocop:enable MethodLength
52
55
  end
@@ -1,4 +1,7 @@
1
+ require 'English'
2
+ require 'bindata'
1
3
  require 'forwardable'
4
+ require 'pio/parse_error'
2
5
 
3
6
  module Pio
4
7
  module OpenFlow
@@ -6,43 +9,124 @@ module Pio
6
9
  class Message
7
10
  extend Forwardable
8
11
 
12
+ # struct ofp_header fields.
9
13
  def_delegators :@format, :open_flow_header
10
14
  def_delegators :open_flow_header, :ofp_version
11
15
  def_delegators :open_flow_header, :message_type
12
16
  def_delegators :open_flow_header, :message_length
13
17
  def_delegators :open_flow_header, :transaction_id
14
18
  def_delegator :open_flow_header, :transaction_id, :xid
19
+
15
20
  def_delegators :@format, :body
16
21
  def_delegator :@format, :body, :user_data
22
+
17
23
  def_delegator :@format, :to_binary_s, :to_binary
18
24
 
25
+ def self.factory(message_type)
26
+ @message_type = message_type
27
+ self
28
+ end
29
+
30
+ def self.klass(message_type)
31
+ @messages.fetch(message_type)
32
+ end
33
+
34
+ def self.inherited(child)
35
+ child.const_set(:MESSAGE_TYPE, @message_type)
36
+ @messages ||= {}
37
+ @messages[@message_type] = child
38
+ end
39
+
40
+ def self.read(raw_data)
41
+ message = allocate
42
+ message.instance_variable_set(:@format, format.read(raw_data))
43
+ message
44
+ rescue BinData::ValidityError
45
+ message_name = name.split('::')[1..-1].join(' ')
46
+ raise Pio::ParseError, "Invalid #{message_name} message."
47
+ end
48
+
49
+ def self.format
50
+ const_get(:Format)
51
+ rescue NameError
52
+ define_message_format const_get(:MESSAGE_TYPE)
53
+ retry
54
+ end
55
+
19
56
  # rubocop:disable MethodLength
20
- def self.def_format(message_type)
21
- str = %(
22
- class Format < BinData::Record
23
- endian :big
24
-
25
- open_flow_header :open_flow_header,
26
- message_type_value: #{message_type}
27
- virtual assert: -> do
28
- open_flow_header.message_type == #{message_type}
29
- end
30
-
31
- body :body
32
- end
57
+ def self.define_message_format(message_type)
58
+ code = %(
59
+ class Format < BinData::Record
60
+ endian :big
61
+
62
+ open_flow_header :open_flow_header,
63
+ message_type_value: #{message_type}
64
+ virtual assert: -> do
65
+ open_flow_header.message_type == #{message_type}
66
+ end
67
+
68
+ #{body_type} :body
69
+ end
33
70
  )
34
- module_eval str
71
+ module_eval code
35
72
  end
36
73
  # rubocop:enable MethodLength
37
74
 
38
- # @reek This method smells of :reek:FeatureEnvy
39
- def initialize(user_options)
40
- header_options = { transaction_id: user_options[:transaction_id] ||
41
- user_options[:xid] || 0 }
42
- format_klass = self.class.const_get(:Format)
43
- @format = format_klass.new(open_flow_header: header_options,
44
- body: user_options)
75
+ def self.body_type
76
+ klass_name = name.split('::').last + 'Body'
77
+ const_get(klass_name)
78
+ klass_name.sub(/.*::/, '')
79
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
80
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
81
+ .tr('-', '_')
82
+ .downcase
83
+ rescue NameError
84
+ 'string'
85
+ end
86
+
87
+ def initialize(user_options = {})
88
+ header_options = parse_header_options(user_options)
89
+ body_options = parse_body_options(user_options)
90
+ @format = self.class.format.new(open_flow_header: header_options,
91
+ body: body_options)
45
92
  end
93
+
94
+ private
95
+
96
+ # This method smells of :reek:FeatureEnvy
97
+ def parse_header_options(options)
98
+ xid = if options.respond_to?(:to_i)
99
+ options.to_i
100
+ elsif options.respond_to?(:fetch)
101
+ options[:transaction_id] || options[:xid] || 0
102
+ else
103
+ fail TypeError
104
+ end
105
+ return { transaction_id: xid } if xid.unsigned_32bit?
106
+ fail(ArgumentError,
107
+ 'Transaction ID should be an unsigned 32-bit integer.')
108
+ end
109
+
110
+ # rubocop:disable MethodLength
111
+ # This method smells of :reek:TooManyStatements
112
+ # This method smells of :reek:FeatureEnvy
113
+ # This method smells of :reek:UtilityFunction
114
+ def parse_body_options(options)
115
+ if options.respond_to?(:fetch)
116
+ options.delete :transaction_id
117
+ options.delete :xid
118
+ dpid = options[:dpid]
119
+ options[:datapath_id] = dpid if dpid
120
+ if options.keys.size > 1
121
+ options
122
+ else
123
+ options[:user_data] || ''
124
+ end
125
+ else
126
+ ''
127
+ end
128
+ end
129
+ # rubocop:enable MethodLength
46
130
  end
47
131
  end
48
132
  end
@@ -9,37 +9,37 @@ module Pio
9
9
  extend Flags
10
10
 
11
11
  # enum ofp_port_config
12
- def_flags :port_config,
13
- [:port_down,
14
- :no_stp,
15
- :no_recv,
16
- :no_recv_stp,
17
- :no_flood,
18
- :no_fwd,
19
- :no_packet_in]
12
+ flags_32bit :port_config,
13
+ [:port_down,
14
+ :no_stp,
15
+ :no_recv,
16
+ :no_recv_stp,
17
+ :no_flood,
18
+ :no_fwd,
19
+ :no_packet_in]
20
20
 
21
21
  # enum ofp_port_state
22
- def_flags :port_state,
23
- link_down: 1 << 0,
24
- stp_listen: 0 << 8,
25
- stp_learn: 1 << 8,
26
- stp_forward: 2 << 8,
27
- stp_block: 3 << 8
22
+ flags_32bit :port_state,
23
+ link_down: 1 << 0,
24
+ stp_listen: 0 << 8,
25
+ stp_learn: 1 << 8,
26
+ stp_forward: 2 << 8,
27
+ stp_block: 3 << 8
28
28
 
29
29
  # enum ofp_port_features
30
- def_flags :port_feature,
31
- [:port_10mb_hd,
32
- :port_10mb_fd,
33
- :port_100mb_hd,
34
- :port_100mb_fd,
35
- :port_1gb_hd,
36
- :port_1gb_fd,
37
- :port_10gb_fd,
38
- :port_copper,
39
- :port_fiber,
40
- :port_autoneg,
41
- :port_pause,
42
- :port_pause_asym]
30
+ flags_32bit :port_feature,
31
+ [:port_10mb_hd,
32
+ :port_10mb_fd,
33
+ :port_100mb_hd,
34
+ :port_100mb_fd,
35
+ :port_1gb_hd,
36
+ :port_1gb_fd,
37
+ :port_10gb_fd,
38
+ :port_copper,
39
+ :port_fiber,
40
+ :port_autoneg,
41
+ :port_pause,
42
+ :port_pause_asym]
43
43
 
44
44
  endian :big
45
45
 
@@ -52,6 +52,10 @@ module Pio
52
52
  port_feature :advertised
53
53
  port_feature :supported
54
54
  port_feature :peer
55
+
56
+ def mac_address
57
+ hardware_address
58
+ end
55
59
  end
56
60
  end
57
61
  end
@@ -9,6 +9,7 @@ module Pio
9
9
  FEATURES_REPLY = 6
10
10
  PACKET_IN = 10
11
11
  PACKET_OUT = 13
12
+ FLOW_MOD = 14
12
13
  end
13
14
  end
14
15
  end
data/lib/pio/packet_in.rb CHANGED
@@ -4,7 +4,7 @@ require 'pio/parse_error'
4
4
 
5
5
  module Pio
6
6
  # OpenFlow 1.0 Packet-In message
7
- class PacketIn < Pio::OpenFlow::Message
7
+ class PacketIn < OpenFlow::Message.factory(OpenFlow::Type::PACKET_IN)
8
8
  # Why is this packet being sent to the controller?
9
9
  # (enum ofp_packet_in_reason)
10
10
  class Reason < BinData::Primitive
@@ -22,7 +22,7 @@ module Pio
22
22
  end
23
23
 
24
24
  # Message body of Packet-In.
25
- class Body < BinData::Record
25
+ class PacketInBody < BinData::Record
26
26
  endian :big
27
27
 
28
28
  uint32 :buffer_id
@@ -42,16 +42,6 @@ module Pio
42
42
  end
43
43
  end
44
44
 
45
- def_format Pio::OpenFlow::Type::PACKET_IN
46
-
47
- def self.read(raw_data)
48
- packet_in = allocate
49
- packet_in.instance_variable_set :@format, Format.read(raw_data)
50
- packet_in
51
- rescue BinData::ValidityError
52
- raise Pio::ParseError, 'Invalid Packet-In message.'
53
- end
54
-
55
45
  def_delegators :body, :buffer_id
56
46
  def_delegators :body, :total_len
57
47
  def_delegators :body, :in_port
@@ -1,75 +1,17 @@
1
1
  require 'bindata'
2
- require 'pio/enqueue'
3
2
  require 'pio/open_flow'
4
- require 'pio/send_out_port'
5
- require 'pio/set_eth_addr'
6
- require 'pio/set_ip_addr'
7
- require 'pio/set_ip_tos'
8
- require 'pio/set_transport_port'
9
- require 'pio/set_vlan_priority'
10
- require 'pio/set_vlan_vid'
11
- require 'pio/strip_vlan_header'
12
3
 
13
4
  module Pio
14
5
  # OpenFlow 1.0 Packet-Out message
15
- class PacketOut < Pio::OpenFlow::Message
16
- # Actions list.
17
- class Actions < BinData::Primitive
18
- ACTION_CLASS = {
19
- 0 => Pio::SendOutPort,
20
- 1 => Pio::SetVlanVid,
21
- 2 => Pio::SetVlanPriority,
22
- 3 => Pio::StripVlanHeader,
23
- 4 => Pio::SetEthSrcAddr,
24
- 5 => Pio::SetEthDstAddr,
25
- 6 => Pio::SetIpSrcAddr,
26
- 7 => Pio::SetIpDstAddr,
27
- 8 => Pio::SetIpTos,
28
- 9 => Pio::SetTransportSrcPort,
29
- 10 => Pio::SetTransportDstPort,
30
- 11 => Pio::Enqueue
31
- }
32
-
33
- endian :big
34
-
35
- uint16 :actions_len, initial_value: -> { binary.length }
36
- string :binary, read_length: :actions_len
37
-
38
- def set(value)
39
- self.binary = [value].flatten.map(&:to_binary).join
40
- end
41
-
42
- # rubocop:disable MethodLength
43
- # This method smells of :reek:TooManyStatements
44
- def get
45
- actions = []
46
- tmp = binary
47
- while tmp.length > 0
48
- type = BinData::Uint16be.read(tmp)
49
- begin
50
- action = ACTION_CLASS.fetch(type).read(tmp)
51
- tmp = tmp[action.message_length..-1]
52
- actions << action
53
- rescue KeyError
54
- raise "action type #{type} is not supported."
55
- end
56
- end
57
- actions
58
- end
59
- # rubocop:enable MethodLength
60
-
61
- def [](index)
62
- get[index]
63
- end
64
- end
65
-
6
+ class PacketOut < OpenFlow::Message.factory(OpenFlow::Type::PACKET_OUT)
66
7
  # Message body of Packet-Out
67
- class Body < BinData::Record
8
+ class PacketOutBody < BinData::Record
68
9
  endian :big
69
10
 
70
11
  uint32 :buffer_id
71
12
  uint16 :in_port
72
- actions :actions
13
+ uint16 :actions_len, initial_value: -> { actions.binary.length }
14
+ actions :actions, length: -> { actions_len }
73
15
  rest :data
74
16
 
75
17
  def empty?
@@ -79,18 +21,6 @@ module Pio
79
21
  def length
80
22
  8 + actions_len + data.length
81
23
  end
82
-
83
- def actions_len
84
- actions.actions_len
85
- end
86
- end
87
-
88
- def_format Pio::OpenFlow::Type::PACKET_OUT
89
-
90
- def self.read(raw_data)
91
- packet_out = allocate
92
- packet_out.instance_variable_set :@format, Format.read(raw_data)
93
- packet_out
94
24
  end
95
25
 
96
26
  def_delegators :body, :buffer_id