Skip to content

Commit 59f682e

Browse files
author
Szymon Mentel
committed
Implement an API for publishing a Net Flow into Dobby database
The implementation in dobby_oflib:publish_net_flow/3 allows publishing Net Flow identifiers along with the OpenFlow Path. The first and the last identifiers of the OF Path hang off the Net Flow identifier. This commit also adds tests for the API.
1 parent a6931e2 commit 59f682e

7 files changed

Lines changed: 305 additions & 60 deletions

File tree

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ compile:
1212
deps:
1313
./rebar get-deps
1414

15-
test: compile
16-
./rebar skip_deps=true eunit
15+
eunit: compile
16+
./rebar -v skip_deps=true eunit
1717

1818
ct: compile
19-
./rebar ct
19+
./rebar -v ct
2020

2121
distclean: clean
2222
./rebar delete-deps

rebar.config

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@
22

33
{deps,
44
[{lager, "2.1.1", {git, "https://github.com/basho/lager", {tag, "2.1.1"}}},
5-
{meck, "0.8.2", {git, "https://github.com/eproxus/meck.git", {tag, "0.8.2"}}}]
5+
{meck, "0.8.2", {git, "https://github.com/eproxus/meck.git", {tag, "0.8.2"}}},
6+
%% TODO: Change to https when opensourcing
7+
{dobby, ".*", {git, "git@github.com:shivarammysore/dobby.git", {branch, master}}},
8+
{ct_tty_hook, ".*", {git, "https://github.com/mpmiszczyk/ct_tty_hook.git", {branch, master}}},
9+
{recon, ".*", {git, "https://github.com/ferd/recon.git", {branch, "master"}}}]
610
}.
711

812
{cover_enabled, true}.
913
{erl_opts, [{parse_transform, lager_transform}, debug_info]}.
1014
{edoc_opts, [{preprocess, true}]}.
15+
{ct_extra_params, "-ct_hooks ct_tty_hook"}.

src/dobby_oflib.erl

Lines changed: 81 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
%%%=============================================================================
22
%%% @copyright (C) 2015, Erlang Solutions Ltd
33
%%% @author Szymon Mentel <szymon.mentel@erlang-solutions.com>
4-
%%% @doc <Module purpose>
4+
%%% @doc API Dobby Client Library for OpenFlow
55
%%% @end
66
%%%=============================================================================
77
-module(dobby_oflib).
@@ -11,23 +11,22 @@
1111
-export([get_path/2,
1212
publish_new_flow/3]).
1313

14-
%% Application callbacks
15-
-export([]).
16-
17-
%% -include_lib("").
18-
%% -include("").
14+
-include_lib("dobby/include/dobby.hrl").
1915

2016
%% -define(MYMAC, "MY").
2117

2218
%%------------------------------------------------------------------------------
2319
%% Types
2420
%%------------------------------------------------------------------------------
2521

26-
%% -record(some_rec, {id :: string()}).
22+
-type openflow_path() ::
23+
[#{DatapathId :: binary() =>
24+
list({OFVersion :: 4 | 5, [flow_mod()]})
25+
}].
2726

28-
%%%=============================================================================
29-
%%% Callbacks
30-
%%%=============================================================================
27+
-type flow_mod() :: {Matches :: [term()],
28+
Instructions :: [term()],
29+
Opts :: [term()]}.
3130

3231
%%%=============================================================================
3332
%%% External functions
@@ -36,18 +35,82 @@
3635
-spec get_path(SrcEndpoint :: binary(), DstEndpoint :: binary()) ->
3736
{ok, Path :: digraph:graph()} | {error, Reason :: term()}.
3837

39-
get_path(SrcEndPoint, DstEndpoint) ->
38+
get_path(SrcEndpoint, DstEndpoint) ->
4039
{ok, digraph:new()}.
4140

42-
-spec publish_new_flow(Src :: binary(), Dst :: binary(), FlowPath) ->
43-
{ok, NetFlowId :: binary()} | {error, Reason :: term()}
44-
when
45-
FlowPath :: #{DatapathId :: binary() => FlowMods :: list(FlowMod)},
46-
FlowMod :: {Matches :: [term()], Instructions :: [term()], Opts :: [term()]}.
41+
%% @doc Publish a Net Flow between two endpoints to Dobby.
42+
%%
43+
%% Publishes a NetFlow identifier between `SrcEndpoint' and `DstEndpoint'
44+
%% that are assumed to be present in Dobby database. It also publishes
45+
%% `OpenFlowPath' that is set of links and identifiers representing OpenFlow
46+
%% entities that provide logical connectivity between the two endpoints.
47+
%% The first and the last identifiers of the `OpenFlowPath' hang off
48+
%% the Net Flow Identifier and their connections to it indicate the beginning
49+
%% an the end of the `OpenFlowPath'.
50+
%%
51+
%% The function returns Net Flow Identifier: `NetFlowId' that can be
52+
%% used for referencing published Net Flow.
53+
-spec publish_new_flow(identifier(), identifier(), openflow_path()) ->
54+
Result when
55+
Result :: {ok, NetFlowId :: endpoint()}
56+
| {error, Reason :: term()}.
4757

48-
publish_new_flow(SrcEndpoint, DstEndpoint, FlowPath) ->
49-
ok.
58+
publish_new_flow(SrcEndpoint, DstEndpoint, OpenFlowPath) ->
59+
NfId = publish_net_flow_identifer(SrcEndpoint, DstEndpoint),
60+
publish_openflow_path(NfId, OpenFlowPath),
61+
NfId.
5062

5163
%%%=============================================================================
5264
%%% Internal functions
5365
%%%=============================================================================
66+
67+
publish_net_flow_identifer(Src, Dst) ->
68+
%% TODO: In transaction
69+
NfId = net_flow_identifier(Src, Dst),
70+
dby:publish(Src, NfId, link_metadata(ep_to_nf, Src), [persistent]),
71+
dby:publish(NfId, Dst, link_metadata(ep_to_nf, NfId), [persistent]),
72+
NfId.
73+
74+
publish_openflow_path(NetFlowId, OpenFlowPath0) ->
75+
FlowPath1 = flatten_openflow_path(OpenFlowPath0),
76+
publish_openflow_path(NetFlowId, FlowPath1, NetFlowId).
77+
78+
publish_openflow_path(NetFlowId, [ExtendedFlowMod | T], LastId)
79+
when LastId =:= NetFlowId ->
80+
Identifier = {Id, _Md} = flow_mod_identifier(ExtendedFlowMod),
81+
LinkMd = link_metadata(of_path_starts_at, {NetFlowId, NetFlowId}),
82+
dby:publish(NetFlowId, Identifier, LinkMd, [persistent]),
83+
publish_openflow_path(NetFlowId, T, Id);
84+
publish_openflow_path(NetFlowId, [ExtendedFlowMod | T], LastId) ->
85+
Identifier = {Id, _Md} = flow_mod_identifier(ExtendedFlowMod),
86+
LinkMd = link_metadata(of_path_forwards_to, {NetFlowId, LastId}),
87+
dby:publish(LastId, Identifier, LinkMd, [persistent]),
88+
publish_openflow_path(NetFlowId, T, Id);
89+
publish_openflow_path(NetFlowId, [], LastId) ->
90+
LinkMd = link_metadata(of_path_ends_at, {NetFlowId, LastId}),
91+
dby:publish(LastId, NetFlowId, LinkMd, [persistent]).
92+
93+
net_flow_identifier(Src, Dst) ->
94+
<<"NF:", Src/binary, ":", Dst/binary>>.
95+
96+
flow_mod_identifier({Dpid, OFVersion, FlowMod}) ->
97+
{_Matches, _Instructions, Opts} = FlowMod,
98+
Cookie = proplists:get_value(cookie, Opts),
99+
{Cookie, #{dpid => Dpid, of_version => OFVersion}}.
100+
101+
link_metadata(Type = ep_to_nf, SrcIdentifier) ->
102+
#{type => Type, src => SrcIdentifier};
103+
link_metadata(Type, {NetFlowId, Src})
104+
when Type =:= of_path_starts_at;
105+
Type =:= of_path_ends_at;
106+
Type =:= of_path_forwards_to ->
107+
#{type => Type, src => Src, net_flow_ids => [NetFlowId]}.
108+
109+
flatten_openflow_path(FlowPath0) ->
110+
Fun = fun({Dpid, {OFVersion, FlowMods}}) ->
111+
[{Dpid, OFVersion, FlowMod} || FlowMod <- FlowMods]
112+
end,
113+
FlowPath1 = lists:map(Fun, FlowPath0),
114+
lists:flatten(FlowPath1).
115+
116+

test/dobby_oflib.test.cfg

Lines changed: 0 additions & 2 deletions
This file was deleted.

test/dobby_oflib_SUITE.erl

Lines changed: 75 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,59 +10,104 @@
1010
%% Note: This directive should only be used in test suites.
1111
-compile(export_all).
1212
-include_lib("common_test/include/ct.hrl").
13+
-include_lib("eunit/include/eunit.hrl").
1314

1415
%%%=============================================================================
1516
%%% Callbacks
1617
%%%=============================================================================
1718

1819

1920
suite() ->
20-
[{timetrap,{minutes,10}}].
21+
[{timetrap,{minutes,10}}].
2122

2223
init_per_suite(Config) ->
23-
Config.
24+
mock_dobby(),
25+
Config.
2426

2527
end_per_suite(_Config) ->
26-
ok.
27-
28-
init_per_group(_GroupName, Config) ->
29-
Config.
30-
31-
end_per_group(_GroupName, _Config) ->
32-
ok.
33-
34-
init_per_testcase(_TestCase, Config) ->
35-
Config.
36-
37-
end_per_testcase(_TestCase, _Config) ->
38-
ok.
39-
40-
groups() ->
41-
[].
28+
unmock_dobby(),
29+
ok.
4230

4331
all() ->
44-
[should_publish_net_flow].
32+
[should_publish_net_flow].
4533

4634
%%%=============================================================================
4735
%%% Testcases
4836
%%%=============================================================================
4937

5038
should_publish_net_flow(_Config) ->
5139
%% GIVEN
52-
FlowPath =
53-
[{"00:00:00:00:0p0:01:00:01",
54-
[{[{in_port,1}],[{apply_actions,[{output,2,no_buffer}]}],[]},
55-
{[{in_port,2}], [{apply_actions,[{output,1,no_buffer}]}], []}]
56-
},
57-
{"00:00:00:00:00:01:00:02",
58-
[{[{in_port,2}],[{apply_actions,[{output,1,no_buffer}]}],[]},
59-
{[{in_port,1}],[{apply_actions,[{output,2,no_buffer}]}],[]}]
60-
}],
40+
FlowPath = dofl_test_utils:flow_path(),
6141
SrcEP = <<"Src">>,
6242
DstEP = <<"Dst">>,
6343

6444
%% WHEN
65-
NetFlowId = dobby_ofclient:publish_new_flow(SrcEP, DstEP, FlowPath),
45+
NetFlowId = dobby_oflib:publish_new_flow(SrcEP, DstEP, FlowPath),
6646

6747
%% THEN
68-
meck:called(dby, publish, [SrcEP, DstEP, ])
48+
meck:wait(2, dby, publish, '_', 2000),
49+
?assert(meck:called(dby, publish,
50+
[SrcEP, NetFlowId, #{type => ep_to_nf, src => SrcEP},
51+
[persistent]])),
52+
?assert(meck:called(dby, publish,
53+
[NetFlowId, DstEP, #{type => ep_to_nf, src => NetFlowId},
54+
[persistent]])),
55+
assert_flow_path_published(NetFlowId, FlowPath).
56+
57+
%%%=============================================================================
58+
%%% Assertions
59+
%%%=============================================================================
60+
61+
62+
assert_flow_path_published(NetFlowId, FlowPath) ->
63+
FlatFlowPath = flatten_flow_path(FlowPath),
64+
assert_flow_path_published(NetFlowId, FlatFlowPath,
65+
_PrevIdentifier = NetFlowId).
66+
67+
assert_flow_path_published(NetFlowId, [FlowMod | T], NetFlowId) ->
68+
MD = #{type => of_path_starts_at,
69+
src => NetFlowId,
70+
net_flow_ids => [NetFlowId]},
71+
R = {Id,_FMD} = flow_mod_identifier(FlowMod),
72+
?assert(meck:called(dby, publish, [NetFlowId, R, MD, [persistent]])),
73+
assert_flow_path_published(NetFlowId, T, Id);
74+
assert_flow_path_published(NetFlowId, [FlowMod | T], LastId) ->
75+
MD = #{type => of_path_forwards_to,
76+
src => LastId,
77+
net_flow_ids => [NetFlowId]},
78+
R = {Id,_FMD} = flow_mod_identifier(FlowMod),
79+
?assert(meck:called(dby, publish, [LastId, R, MD, [persistent]])),
80+
assert_flow_path_published(NetFlowId, T, Id);
81+
assert_flow_path_published(NetFlowId, [], LastId) ->
82+
MD = #{type => of_path_ends_at,
83+
src => LastId,
84+
net_flow_ids => [NetFlowId]},
85+
?assert(meck:called(dby, publish, [LastId, NetFlowId, MD, [persistent]])).
86+
87+
%%%=============================================================================
88+
%%% Internal functions
89+
%%%=============================================================================
90+
91+
mock_dobby() ->
92+
ok = meck:expect(dby, publish,
93+
fun(SrcEp, DstEp) ->
94+
<<"NFL:", SrcEp/binary, ":", DstEp/binary>>
95+
end).
96+
unmock_dobby() ->
97+
ok = meck:unload(dby).
98+
99+
flow_mod_identifier({Dpid, OFVersion, FlowMod}) ->
100+
{_Matches, _Instructions, Opts} = FlowMod,
101+
Cookie = proplists:get_value(cookie, Opts),
102+
{Cookie, #{dpid => Dpid, of_version => OFVersion}}.
103+
104+
flatten_flow_path(FlowPath0) ->
105+
Fun = fun({Dpid, {OFVersion, FlowMods}}) ->
106+
[{Dpid, OFVersion, FlowMod} || FlowMod <- FlowMods]
107+
end,
108+
FlowPath1 = lists:map(Fun, FlowPath0),
109+
lists:flatten(FlowPath1).
110+
111+
112+
113+

test/dofl_test_utils.erl

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@
88
-copyright("2015, Erlang Solutions Ltd.").
99

1010
%% API
11-
-export([figure4/0]).
11+
-export([figure4/0,
12+
flow_path/0,
13+
flow_path_to_identifiers/1]).
1214

1315
-include_lib("eunit/include/eunit.hrl").
1416

1517
%%%=============================================================================
1618
%%% External functions
1719
%%%=============================================================================
1820

21+
%% @doc
1922
%% Represents graph from figure 4 in
2023
%% https://docs.google.com/a/erlang-solutions.com/document/d/1HmdKtNvAR1f8JQeyY13kzGMUjdAgPDE9tuszo2OFXwM/edit#heading=h.ofp54z1pu56t
2124
figure4() ->
@@ -35,6 +38,39 @@ figure4() ->
3538
add_link({OFP1, OFP2, connected_to}, Graph),
3639
Graph.
3740

41+
%% @doc
42+
%% Returns sample flow path that can be passed to
43+
%% `dobby_oflib:publish_net_flow/3)'.
44+
flow_path() ->
45+
[{<<"00:00:00:00:00:01:00:01">>,
46+
{_OFVersion = 4,
47+
[{[{in_port,1}],
48+
[{apply_actions,[{output,2,no_buffer}]}],
49+
[{table_id,0}, {priority, 100},
50+
{idle_timeout, 0}, {idle_timeout, 0},
51+
{cookie, <<0,0,0,0,0,0,0,101>>},
52+
{cookie_mask, <<0,0,0,0,0,0,0,0>>}]}]
53+
}},
54+
{<<"00:00:00:00:00:01:00:02">>,
55+
{_OFVersion = 4,
56+
[{[{in_port,2}],
57+
[{apply_actions,[{output,1,no_buffer}]}],
58+
[{table_id,0}, {priority, 100},
59+
{idle_timeout, 0}, {idle_timeout, 0},
60+
{cookie, <<0,0,0,0,0,0,0,102>>},
61+
{cookie_mask, <<0,0,0,0,0,0,0,0>>}]}]}
62+
}].
63+
64+
%% @doc
65+
%% Transletes `FlowPath' as returned by ?MODULE:figure4() into a list
66+
%% of subsequent identifiers.
67+
flow_path_to_identifiers(FlowPath) ->
68+
Fun = fun({_Dpid, {_OFVersion, FlowMods}}) ->
69+
[proplists:get_value(cookie, O) || {_M, _I, O} <-FlowMods]
70+
end,
71+
lists:map(Fun, FlowPath).
72+
73+
3874
%%%=============================================================================
3975
%%% Internal functions
4076
%%%=============================================================================
@@ -63,11 +99,7 @@ id(N, of_switch) ->
6399
list_to_binary("OFS" ++ integer_to_list(N)).
64100

65101
no_to_dpid(N) ->
66-
"00:00:00:00:00:01:00:0" ++ integer_to_list(N).
67-
68-
dpid_to_no(Dpid) ->
69-
[No | _ ] = lists:reverse(Dpid),
70-
binary_to_integer(<<No>>).
102+
list_to_binary("00:00:00:00:00:01:00:0" ++ integer_to_list(N)).
71103

72104
ip(N) ->
73105
{10, 0, 0, N}.

0 commit comments

Comments
 (0)