Skip to content

Commit d567e1f

Browse files
authored
Merge pull request #80 from mzbenami/mbenami/learn_action
add Learn action with LearnedFlow
2 parents 808aafc + 1e0dc70 commit d567e1f

File tree

5 files changed

+256
-23
lines changed

5 files changed

+256
-23
lines changed

ovs/action.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ var (
6767

6868
// errOutputFieldEmpty is returned when OutputField is called with field set to the empty string.
6969
errOutputFieldEmpty = errors.New("field for action output (output:field syntax) is empty")
70+
71+
// errLearnedNil is returned when Learn is called with a nil *LearnedFlow.
72+
errLearnedNil = errors.New("learned flow for action learn is nil")
7073
)
7174

7275
// Action strings in lower case, as those are compared to the lower case letters
@@ -190,6 +193,7 @@ const (
190193
patOutputField = "output:%s"
191194
patResubmitPort = "resubmit:%s"
192195
patResubmitPortTable = "resubmit(%s,%s)"
196+
patLearn = "learn(%s)"
193197
)
194198

195199
// ConnectionTracking sends a packet through the host's connection tracker.
@@ -643,6 +647,37 @@ func (a *moveAction) MarshalText() ([]byte, error) {
643647
return bprintf("move:%s->%s", a.src, a.dst), nil
644648
}
645649

650+
// Learn dynamically installs a LearnedFlow.
651+
func Learn(learned *LearnedFlow) Action {
652+
return &learnAction{
653+
learned: learned,
654+
}
655+
}
656+
657+
// A learnAction is an Action used by Learn.
658+
type learnAction struct {
659+
learned *LearnedFlow
660+
}
661+
662+
// GoString implements Action.
663+
func (a *learnAction) GoString() string {
664+
return fmt.Sprintf("ovs.Learn(%#v)", a.learned)
665+
}
666+
667+
// MarshalText implements Action.
668+
func (a *learnAction) MarshalText() ([]byte, error) {
669+
if a.learned == nil {
670+
return nil, errLearnedNil
671+
}
672+
673+
l, err := a.learned.MarshalText()
674+
if err != nil {
675+
return nil, err
676+
}
677+
678+
return bprintf(patLearn, l), nil
679+
}
680+
646681
// validARPOP indicates if an ARP OP is out of range. It should be in the range
647682
// 1-4.
648683
func validARPOP(op uint16) bool {

ovs/action_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,60 @@ func TestOutputField(t *testing.T) {
667667
}
668668
}
669669

670+
func TestLearn(t *testing.T) {
671+
var tests = []struct {
672+
desc string
673+
a Action
674+
action string
675+
err error
676+
}{
677+
{
678+
desc: "learn ok",
679+
a: Learn(&LearnedFlow{
680+
DeleteLearned: true,
681+
FinHardTimeout: 10,
682+
Matches: []Match{DataLinkType(0x800)},
683+
Actions: []Action{OutputField("in_port"), Load("2", "tp_dst")},
684+
}),
685+
action: `learn(priority=0,dl_type=0x0800,table=0,idle_timeout=0,fin_hard_timeout=10,delete_learned,output:in_port,load:2->tp_dst)`,
686+
},
687+
{
688+
desc: "prohibited learned action, mod_tp_dst",
689+
a: Learn(&LearnedFlow{
690+
DeleteLearned: true,
691+
FinHardTimeout: 10,
692+
Matches: []Match{DataLinkType(0x800)},
693+
Actions: []Action{ModTransportDestinationPort(1)},
694+
}),
695+
err: errInvalidLearnedActions,
696+
},
697+
{
698+
desc: "nil learned flow",
699+
a: Learn(nil),
700+
err: errLearnedNil,
701+
},
702+
}
703+
704+
for _, tt := range tests {
705+
t.Run(tt.desc, func(t *testing.T) {
706+
action, err := tt.a.MarshalText()
707+
708+
if want, got := tt.err, err; want != got {
709+
t.Fatalf("unexpected error:\n- want: %v\n- got: %v",
710+
want, got)
711+
}
712+
if err != nil {
713+
return
714+
}
715+
716+
if want, got := tt.action, string(action); want != got {
717+
t.Fatalf("unexpected Action:\n- want: %q\n- got: %q",
718+
want, got)
719+
}
720+
})
721+
}
722+
}
723+
670724
func TestActionGoString(t *testing.T) {
671725
tests := []struct {
672726
a Action
@@ -748,6 +802,15 @@ func TestActionGoString(t *testing.T) {
748802
a: OutputField("in_port"),
749803
s: `ovs.OutputField("in_port")`,
750804
},
805+
{
806+
a: Learn(&LearnedFlow{
807+
DeleteLearned: true,
808+
FinHardTimeout: 10,
809+
Matches: []Match{DataLinkType(0x800)},
810+
Actions: []Action{OutputField("in_port")},
811+
}),
812+
s: `ovs.Learn(&ovs.LearnedFlow{Priority:0, InPort:0, Matches:[]ovs.Match{ovs.DataLinkType(0x0800)}, Table:0, IdleTimeout:0, Cookie:0x0, Actions:[]ovs.Action{ovs.OutputField("in_port")}, DeleteLearned:true, FinHardTimeout:10})`,
813+
},
751814
}
752815

753816
for _, tt := range tests {

ovs/actionparser_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func Test_actionParser(t *testing.T) {
7676
want, got)
7777
}
7878

79-
as, err := (&Flow{Actions: actions}).marshalActions()
79+
as, err := marshalActions(actions)
8080
if err != nil {
8181
t.Fatalf("unexpected error: %v", err)
8282
}

ovs/flow.go

Lines changed: 112 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,12 @@ const (
3030
// Possible errors which may be encountered while marshaling or unmarshaling
3131
// a Flow.
3232
var (
33-
errActionsWithDrop = errors.New("Flow actions include drop, but multiple actions specified")
34-
errInvalidActions = errors.New("invalid actions for Flow")
35-
errNoActions = errors.New("no actions defined for Flow")
36-
errNotEnoughElements = errors.New("not enough elements for valid Flow")
37-
errPriorityNotFirst = errors.New("priority field is not first in Flow")
33+
errActionsWithDrop = errors.New("Flow actions include drop, but multiple actions specified")
34+
errInvalidActions = errors.New("invalid actions for Flow")
35+
errNoActions = errors.New("no actions defined for Flow")
36+
errNotEnoughElements = errors.New("not enough elements for valid Flow")
37+
errPriorityNotFirst = errors.New("priority field is not first in Flow")
38+
errInvalidLearnedActions = errors.New("invalid actions for LearnedFlow")
3839
)
3940

4041
// A Protocol is an OpenFlow protocol designation accepted by Open vSwitch.
@@ -66,6 +67,20 @@ type Flow struct {
6667
Actions []Action
6768
}
6869

70+
// A LearnedFlow is defined as part of the Learn action.
71+
type LearnedFlow struct {
72+
Priority int
73+
InPort int
74+
Matches []Match
75+
Table int
76+
IdleTimeout int
77+
Cookie uint64
78+
Actions []Action
79+
80+
DeleteLearned bool
81+
FinHardTimeout int
82+
}
83+
6984
var _ error = &FlowError{}
7085

7186
// A FlowError is an error encountered while marshaling or unmarshaling
@@ -105,6 +120,10 @@ const (
105120
hardAge = "hard_age"
106121
idleAge = "idle_age"
107122

123+
// Variables used in LearnedFlows only.
124+
deleteLearned = "delete_learned"
125+
finHardTimeout = "fin_hard_timeout"
126+
108127
portLOCAL = "LOCAL"
109128
)
110129

@@ -120,12 +139,12 @@ func (f *Flow) MarshalText() ([]byte, error) {
120139
}
121140
}
122141

123-
actions, err := f.marshalActions()
142+
actions, err := marshalActions(f.Actions)
124143
if err != nil {
125144
return nil, err
126145
}
127146

128-
matches, err := f.marshalMatches()
147+
matches, err := marshalMatches(f.Matches)
129148
if err != nil {
130149
return nil, err
131150
}
@@ -183,6 +202,81 @@ func (f *Flow) MarshalText() ([]byte, error) {
183202
return b, nil
184203
}
185204

205+
// MarshalText marshals a LearnedFlow into its textual form.
206+
func (f *LearnedFlow) MarshalText() ([]byte, error) {
207+
if len(f.Actions) == 0 {
208+
return nil, &FlowError{
209+
Err: errNoActions,
210+
}
211+
}
212+
213+
// A learned flow can have a limited set of actions, namely `load` and `output:field`.
214+
for _, a := range f.Actions {
215+
switch a.(type) {
216+
case *loadSetFieldAction:
217+
if a.(*loadSetFieldAction).typ != actionLoad {
218+
return nil, errInvalidLearnedActions
219+
}
220+
case *outputFieldAction:
221+
default:
222+
return nil, errInvalidLearnedActions
223+
}
224+
}
225+
226+
actions, err := marshalActions(f.Actions)
227+
if err != nil {
228+
return nil, err
229+
}
230+
231+
matches, err := marshalMatches(f.Matches)
232+
if err != nil {
233+
return nil, err
234+
}
235+
236+
b := make([]byte, len(priorityBytes))
237+
copy(b, priorityBytes)
238+
239+
b = strconv.AppendInt(b, int64(f.Priority), 10)
240+
241+
if f.InPort != 0 {
242+
b = append(b, ","+inPort+"="...)
243+
244+
// Special case, InPortLOCAL is converted to the literal string LOCAL
245+
if f.InPort == PortLOCAL {
246+
b = append(b, portLOCAL...)
247+
} else {
248+
b = strconv.AppendInt(b, int64(f.InPort), 10)
249+
}
250+
}
251+
252+
if len(matches) > 0 {
253+
b = append(b, ","+strings.Join(matches, ",")...)
254+
}
255+
256+
b = append(b, ","+table+"="...)
257+
b = strconv.AppendInt(b, int64(f.Table), 10)
258+
259+
b = append(b, ","+idleTimeout+"="...)
260+
b = strconv.AppendInt(b, int64(f.IdleTimeout), 10)
261+
262+
b = append(b, ","+finHardTimeout+"="...)
263+
b = strconv.AppendInt(b, int64(f.FinHardTimeout), 10)
264+
265+
if f.DeleteLearned {
266+
b = append(b, ","+deleteLearned...)
267+
}
268+
269+
if f.Cookie > 0 {
270+
// Hexadecimal cookies are much easier to read.
271+
b = append(b, ","+cookie+"="...)
272+
b = append(b, paddedHexUint64(f.Cookie)...)
273+
}
274+
275+
b = append(b, ","+strings.Join(actions, ",")...)
276+
277+
return b, nil
278+
}
279+
186280
// UnmarshalText unmarshals flow text into a Flow.
187281
func (f *Flow) UnmarshalText(b []byte) error {
188282
// Make a copy per documentation for encoding.TextUnmarshaler.
@@ -339,28 +433,28 @@ func (f *Flow) MatchFlow() *MatchFlow {
339433
}
340434
}
341435

342-
// marshalActions marshals all Actions in a Flow to their text form.
343-
func (f *Flow) marshalActions() ([]string, error) {
344-
fns := make([]func() ([]byte, error), 0, len(f.Actions))
345-
for _, fn := range f.Actions {
436+
// marshalActions marshals all provided Actions to their text form.
437+
func marshalActions(aa []Action) ([]string, error) {
438+
fns := make([]func() ([]byte, error), 0, len(aa))
439+
for _, fn := range aa {
346440
fns = append(fns, fn.MarshalText)
347441
}
348442

349-
return f.marshalFunctions(fns)
443+
return marshalFunctions(fns)
350444
}
351445

352-
// marshalMatches marshals all Matches in a Flow to their text form.
353-
func (f *Flow) marshalMatches() ([]string, error) {
354-
fns := make([]func() ([]byte, error), 0, len(f.Matches))
355-
for _, fn := range f.Matches {
446+
// marshalMatches marshals all provided Matches to their text form.
447+
func marshalMatches(mm []Match) ([]string, error) {
448+
fns := make([]func() ([]byte, error), 0, len(mm))
449+
for _, fn := range mm {
356450
fns = append(fns, fn.MarshalText)
357451
}
358452

359-
return f.marshalFunctions(fns)
453+
return marshalFunctions(fns)
360454
}
361455

362456
// marshalFunctions marshals a slice of functions to their text form.
363-
func (f *Flow) marshalFunctions(fns []func() ([]byte, error)) ([]string, error) {
457+
func marshalFunctions(fns []func() ([]byte, error)) ([]string, error) {
364458
out := make([]string, 0, len(fns))
365459
for _, fn := range fns {
366460
o, err := fn()

0 commit comments

Comments
 (0)