@@ -58,6 +58,8 @@ const (
5858 tcLatencyFaultExistsCommandOutput = `[{"kind":"netem","handle":"10:","parent":"1:1","options":{"limit":1000,"delay":{"delay":123456789,"jitter":4567,"correlation":0},"ecn":false,"gap":0}}]`
5959 tcLossFaultExistsCommandOutput = `[{"kind":"netem","handle":"10:","dev":"eth0","parent":"1:1","options":{"limit":1000,"loss-random":{"loss":0.06,"correlation":0},"ecn":false,"gap":0}}]`
6060 tcCommandEmptyOutput = `[]`
61+ startEndpoint = "/api/%s/fault/v1/%s/start"
62+ stopEndpoint = "/api/%s/fault/v1/%s/stop"
6163)
6264
6365var (
@@ -1899,9 +1901,13 @@ func generateStopNetworkPacketLossTestCases() []networkFaultInjectionTestCase {
18991901 setExecExpectations : func (exec * mock_execwrapper.MockExec , ctrl * gomock.Controller ) {
19001902 ctx , cancel := context .WithTimeout (context .Background (), ctxTimeoutDuration )
19011903 mockCMD := mock_execwrapper .NewMockCmd (ctrl )
1902- exec .EXPECT ().NewExecContextWithTimeout (gomock .Any (), gomock .Any ()).Times (1 ).Return (ctx , cancel )
1903- exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (mockCMD )
1904- mockCMD .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte (tcLatencyFaultExistsCommandOutput ), nil )
1904+ gomock .InOrder (
1905+ exec .EXPECT ().NewExecContextWithTimeout (gomock .Any (), gomock .Any ()).Times (1 ).Return (ctx , cancel ),
1906+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (mockCMD ),
1907+ mockCMD .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte (tcLossFaultExistsCommandOutput ), nil ),
1908+ )
1909+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (2 ).Return (mockCMD )
1910+ mockCMD .EXPECT ().CombinedOutput ().Times (2 ).Return ([]byte ("" ), nil )
19051911 },
19061912 },
19071913 {
@@ -2047,3 +2053,262 @@ func TestCheckNetworkPacketLoss(t *testing.T) {
20472053 tcs := generateCheckNetworkPacketLossTestCases ()
20482054 testNetworkFaultInjectionCommon (t , tcs , NetworkFaultPath (types .PacketLossFaultType , types .CheckNetworkFaultPostfix ))
20492055}
2056+
2057+ func TestNetworkFaultRequestOrdering (t * testing.T ) {
2058+ tcs := []struct {
2059+ name string
2060+ faultType string
2061+ requestBody interface {}
2062+ setAgentStateExpectations func (agentState * mock_state.MockAgentState , netConfigClient * netconfig.NetworkConfigClient )
2063+ setExecExpectations func (exec * mock_execwrapper.MockExec , ctrl * gomock.Controller )
2064+ }{
2065+ {
2066+ name : types .BlackHolePortFaultType + "request ordering" ,
2067+ faultType : types .BlackHolePortFaultType ,
2068+ requestBody : happyBlackHolePortReqBody ,
2069+ setAgentStateExpectations : func (agentState * mock_state.MockAgentState , netConfigClient * netconfig.NetworkConfigClient ) {
2070+ agentState .EXPECT ().GetTaskMetadataWithTaskNetworkConfig (endpointId , netConfigClient ).
2071+ Return (happyTaskResponse , nil ).
2072+ Times (2 )
2073+ },
2074+ setExecExpectations : func (exec * mock_execwrapper.MockExec , ctrl * gomock.Controller ) {
2075+ startCtx , startCancel := context .WithTimeout (context .Background (), ctxTimeoutDuration )
2076+ stopCtx , stopCancel := context .WithTimeout (context .Background (), ctxTimeoutDuration )
2077+ cmdExec := mock_execwrapper .NewMockCmd (ctrl )
2078+ // We want to ensure that the start fault request executes and finishes first before the stop fault request.
2079+ // We can enforce the ordering of exec mock calls.
2080+ gomock .InOrder (
2081+ // Exec mocks for start black hole port request
2082+ exec .EXPECT ().NewExecContextWithTimeout (gomock .Any (), gomock .Any ()).Do (func (_ , _ interface {}) {
2083+ // Sleep for 2 seconds to mock that the request is taking some time
2084+ time .Sleep (2 * time .Second )
2085+ }).Times (1 ).Return (startCtx , startCancel ),
2086+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2087+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte (iptablesChainNotFoundError ), errors .New ("exit status 1" )),
2088+ exec .EXPECT ().ConvertToExitError (gomock .Any ()).Times (1 ).Return (nil , true ),
2089+ exec .EXPECT ().GetExitCode (gomock .Any ()).Times (1 ).Return (1 ),
2090+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2091+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte {}, nil ),
2092+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2093+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte {}, nil ),
2094+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2095+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte {}, nil ),
2096+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2097+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte {}, nil ),
2098+
2099+ // Exec mocks for stop black hole port request
2100+ exec .EXPECT ().NewExecContextWithTimeout (gomock .Any (), gomock .Any ()).Times (1 ).Return (stopCtx , stopCancel ),
2101+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2102+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte {}, nil ),
2103+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2104+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte {}, nil ),
2105+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2106+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte {}, nil ),
2107+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2108+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte {}, nil ),
2109+ )
2110+ },
2111+ },
2112+ {
2113+ name : types .LatencyFaultType + "request ordering" ,
2114+ faultType : types .LatencyFaultType ,
2115+ requestBody : happyNetworkLatencyReqBody ,
2116+ setAgentStateExpectations : func (agentState * mock_state.MockAgentState , netConfigClient * netconfig.NetworkConfigClient ) {
2117+ agentState .EXPECT ().GetTaskMetadataWithTaskNetworkConfig (endpointId , netConfigClient ).
2118+ Return (happyTaskResponse , nil ).
2119+ Times (2 )
2120+ },
2121+ setExecExpectations : func (exec * mock_execwrapper.MockExec , ctrl * gomock.Controller ) {
2122+ startCtx , startCancel := context .WithTimeout (context .Background (), ctxTimeoutDuration )
2123+ stopCtx , stopCancel := context .WithTimeout (context .Background (), ctxTimeoutDuration )
2124+ cmdExec := mock_execwrapper .NewMockCmd (ctrl )
2125+ // We want to ensure that the start fault request executes and finishes first before the stop fault request.
2126+ // We can enforce the ordering of exec mock calls.
2127+ gomock .InOrder (
2128+ // Exec mocks for start latency request
2129+ exec .EXPECT ().NewExecContextWithTimeout (gomock .Any (), gomock .Any ()).Do (func (_ , _ interface {}) {
2130+ // Sleep for 2 seconds to mock that the request is taking some time
2131+ time .Sleep (2 * time .Second )
2132+ }).Times (1 ).Return (startCtx , startCancel ),
2133+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2134+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte (tcCommandEmptyOutput ), nil ),
2135+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2136+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte (tcCommandEmptyOutput ), nil ),
2137+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2138+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte (tcCommandEmptyOutput ), nil ),
2139+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2140+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte (tcCommandEmptyOutput ), nil ),
2141+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2142+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte (tcCommandEmptyOutput ), nil ),
2143+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2144+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte (tcCommandEmptyOutput ), nil ),
2145+
2146+ // Exec mocks for stop latency request
2147+ exec .EXPECT ().NewExecContextWithTimeout (gomock .Any (), gomock .Any ()).Times (1 ).Return (stopCtx , stopCancel ),
2148+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2149+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte (tcLatencyFaultExistsCommandOutput ), nil ),
2150+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2151+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte ("" ), nil ),
2152+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2153+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte ("" ), nil ),
2154+ )
2155+ },
2156+ },
2157+ {
2158+ name : types .PacketLossFaultType + "request ordering" ,
2159+ faultType : types .PacketLossFaultType ,
2160+ requestBody : happyNetworkPacketLossReqBody ,
2161+ setAgentStateExpectations : func (agentState * mock_state.MockAgentState , netConfigClient * netconfig.NetworkConfigClient ) {
2162+ agentState .EXPECT ().GetTaskMetadataWithTaskNetworkConfig (endpointId , netConfigClient ).
2163+ Return (happyTaskResponse , nil ).
2164+ Times (2 )
2165+ },
2166+ setExecExpectations : func (exec * mock_execwrapper.MockExec , ctrl * gomock.Controller ) {
2167+ startCtx , startCancel := context .WithTimeout (context .Background (), ctxTimeoutDuration )
2168+ stopCtx , stopCancel := context .WithTimeout (context .Background (), ctxTimeoutDuration )
2169+ cmdExec := mock_execwrapper .NewMockCmd (ctrl )
2170+ // We want to ensure that the start fault request executes and finishes first before the stop fault request.
2171+ // We can enforce the ordering of exec mock calls.
2172+ gomock .InOrder (
2173+ // Exec mocks for start packet loss request
2174+ exec .EXPECT ().NewExecContextWithTimeout (gomock .Any (), gomock .Any ()).Do (func (_ , _ interface {}) {
2175+ // Sleep for 2 seconds to mock that the request is taking some time
2176+ time .Sleep (2 * time .Second )
2177+ }).Times (1 ).Return (startCtx , startCancel ),
2178+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2179+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte (tcCommandEmptyOutput ), nil ),
2180+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2181+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte (tcCommandEmptyOutput ), nil ),
2182+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2183+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte (tcCommandEmptyOutput ), nil ),
2184+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2185+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte (tcCommandEmptyOutput ), nil ),
2186+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2187+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte (tcCommandEmptyOutput ), nil ),
2188+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2189+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte (tcCommandEmptyOutput ), nil ),
2190+
2191+ // Exec mocks for stop packet loss request
2192+ exec .EXPECT ().NewExecContextWithTimeout (gomock .Any (), gomock .Any ()).Times (1 ).Return (stopCtx , stopCancel ),
2193+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2194+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte (tcLossFaultExistsCommandOutput ), nil ),
2195+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2196+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte ("" ), nil ),
2197+ exec .EXPECT ().CommandContext (gomock .Any (), gomock .Any (), gomock .Any ()).Times (1 ).Return (cmdExec ),
2198+ cmdExec .EXPECT ().CombinedOutput ().Times (1 ).Return ([]byte ("" ), nil ),
2199+ )
2200+ },
2201+ },
2202+ }
2203+
2204+ for _ , tc := range tcs {
2205+ t .Run (tc .name , func (t * testing.T ) {
2206+ // Mocks
2207+ ctrl := gomock .NewController (t )
2208+ defer ctrl .Finish ()
2209+
2210+ agentState := mock_state .NewMockAgentState (ctrl )
2211+ metricsFactory := mock_metrics .NewMockEntryFactory (ctrl )
2212+
2213+ router := mux .NewRouter ()
2214+ mockExec := mock_execwrapper .NewMockExec (ctrl )
2215+ handler := New (agentState , metricsFactory , mockExec )
2216+ networkConfigClient := netconfig .NewNetworkConfigClient ()
2217+
2218+ tc .setAgentStateExpectations (agentState , networkConfigClient )
2219+ tc .setExecExpectations (mockExec , ctrl )
2220+
2221+ var startHandleMethod , stopHandleMethod func (http.ResponseWriter , * http.Request )
2222+ switch tc .faultType {
2223+ case types .BlackHolePortFaultType :
2224+ startHandleMethod = handler .StartNetworkBlackholePort ()
2225+ stopHandleMethod = handler .StopNetworkBlackHolePort ()
2226+ case types .LatencyFaultType :
2227+ startHandleMethod = handler .StartNetworkLatency ()
2228+ stopHandleMethod = handler .StopNetworkLatency ()
2229+ case types .PacketLossFaultType :
2230+ startHandleMethod = handler .StartNetworkPacketLoss ()
2231+ stopHandleMethod = handler .StopNetworkPacketLoss ()
2232+ default :
2233+ t .Error ("Unrecognized network fault type" )
2234+ }
2235+ router .HandleFunc (
2236+ NetworkFaultPath (tc .faultType , types .StartNetworkFaultPostfix ),
2237+ startHandleMethod ,
2238+ ).Methods (http .MethodPost )
2239+
2240+ router .HandleFunc (
2241+ NetworkFaultPath (tc .faultType , types .StopNetworkFaultPostfix ),
2242+ stopHandleMethod ,
2243+ ).Methods (http .MethodPost )
2244+
2245+ var requestBody io.Reader
2246+ reqBodyBytes , err := json .Marshal (tc .requestBody )
2247+ require .NoError (t , err )
2248+ requestBody = bytes .NewReader (reqBodyBytes )
2249+ startReq , err := http .NewRequest (http .MethodPost , fmt .Sprintf (startEndpoint , endpointId , tc .faultType ), requestBody )
2250+ require .NoError (t , err )
2251+
2252+ ch1 := make (chan struct {
2253+ int
2254+ error
2255+ })
2256+
2257+ reqBodyBytes , err = json .Marshal (tc .requestBody )
2258+ require .NoError (t , err )
2259+ requestBody = bytes .NewReader (reqBodyBytes )
2260+ stopReq , err := http .NewRequest (http .MethodPost , fmt .Sprintf (stopEndpoint , endpointId , tc .faultType ), requestBody )
2261+ require .NoError (t , err )
2262+
2263+ ch2 := make (chan struct {
2264+ int
2265+ error
2266+ })
2267+
2268+ // Make an asynchronous Start request first
2269+ go makeAsyncRequest (router , startReq , ch1 )
2270+
2271+ // Waiting a bit before sending the stop request
2272+ time .Sleep (1 * time .Second )
2273+
2274+ // Make an asynchronous Stop request second
2275+ go makeAsyncRequest (router , stopReq , ch2 )
2276+
2277+ // Waiting to get the status code of the start request
2278+ resp1 := <- ch1
2279+ require .NoError (t , resp1 .error )
2280+ assert .Equal (t , http .StatusOK , resp1 .int )
2281+
2282+ // Waiting to get the status code of the stop request
2283+ resp2 := <- ch2
2284+ require .NoError (t , resp2 .error )
2285+ assert .Equal (t , http .StatusOK , resp2 .int )
2286+ })
2287+ }
2288+ }
2289+
2290+ // Helper function for making asynchronous mock HTTP requests
2291+ func makeAsyncRequest (router * mux.Router , req * http.Request , ch chan <- struct {
2292+ int
2293+ error
2294+ }) {
2295+ defer close (ch )
2296+
2297+ // Makes a mock HTTP request
2298+ recorder := httptest .NewRecorder ()
2299+ router .ServeHTTP (recorder , req )
2300+
2301+ var actualResponseBody types.NetworkFaultInjectionResponse
2302+ err := json .Unmarshal (recorder .Body .Bytes (), & actualResponseBody )
2303+ if err != nil {
2304+ ch <- struct {
2305+ int
2306+ error
2307+ }{- 1 , err }
2308+ } else {
2309+ ch <- struct {
2310+ int
2311+ error
2312+ }{recorder .Code , nil }
2313+ }
2314+ }
0 commit comments