Skip to content

Commit 4cfce43

Browse files
authored
[dualtor] Add nic_simulator (#5413)
### Approach #### What is the motivation for this PR? Add `nic_simulator` to act as gRPC server to respond to requests from DUT operates in dualtor `active-active` mode. Signed-off-by: Longxiang Lyu <[email protected]> #### How did you do it? Add script `nic_simulator.py` to start per-port gRPC server for a dualtor `active-active` setup testbed. It should run in the server NiCs namespace, and it basically does the following few things after it starts: 1. scans for all the ports in the namespace that follows the server NiC name pattern(`eth%d`) and finds its corresponding OVS bridge, the bridge will be verified to have the following topology: ``` +--------------+ PTF (host_if) --+ +----- upper_if | OVS bridge | netns (server_nic) --+ +----- lower_if +--------------+ ``` 2. starts to set up the flows for each OVS bridge collected in step1, the initial forwarding state is `(active, active)`. * the flows added: * `FLOW I`: for packets from `upper_if`, it will be duplicated to `host_if` and `server_nic`. * `FLOW II`: for packets from `lower_if`, it will be duplicated to `host_if` and `server_nic`. * `FLOW III`: for packets from `server_nic`, it will be duplicated to `upper_if` and `lower_if`. * `FLOW IV`: for packets from `host_if`, it will be directed to either `upper_if` or `lower_if`. 3. starts the gRPC services(one port, one server) to query/set the forwarding state of the bridge. * if upper ToR forwarding state is set to `standby`, `nic_simulator` will modify `FLOW IV` to let packets from `host_if` only be directed to lower ToR(`active`) **TODO** 1. Add RPC calls to cover testing scenarios like link drop and link down. 2. Add client authentication. 3. Implement `RPC` call `QueryOperationPortState` 4. add logging interceptors to the RPC calls. #### How did you verify/test it? 1. run the `nic_simulator.py` 2. run the `nic_simulator_client.py` to verify `nic_simulator` operations.
1 parent fff6bf1 commit 4cfce43

8 files changed

+1348
-0
lines changed

ansible/dualtor/nic_simulator/nic_simulator.py

Lines changed: 743 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#!/usr/bin/env python3
2+
import argparse
3+
from urllib import response
4+
import grpc
5+
6+
from collections import namedtuple
7+
8+
import nic_simulator_grpc_service_pb2
9+
import nic_simulator_grpc_service_pb2_grpc
10+
import nic_simulator_grpc_mgmt_service_pb2
11+
import nic_simulator_grpc_mgmt_service_pb2_grpc
12+
13+
14+
class MetadataInterceptor(grpc.UnaryUnaryClientInterceptor):
15+
16+
class _ClientCallDetails(
17+
namedtuple(
18+
'_ClientCallDetails',
19+
('method', 'timeout', 'metadata', 'credentials')),
20+
grpc.ClientCallDetails):
21+
"""Wrapper class for initializing a new ClientCallDetails instance.
22+
"""
23+
pass
24+
25+
def __init__(self, injected_meta):
26+
self.injected_meta = injected_meta
27+
28+
def intercept_unary_unary(self, continuation, client_call_details, request):
29+
30+
if client_call_details.metadata is None:
31+
metadata = []
32+
else:
33+
metadata = list(client_call_details.metadata)
34+
35+
metadata.append(self.injected_meta)
36+
37+
client_call_details = self._ClientCallDetails(
38+
client_call_details.method,
39+
client_call_details.timeout,
40+
metadata,
41+
client_call_details.credentials
42+
)
43+
return continuation(client_call_details, request)
44+
45+
46+
def parse_args():
47+
parser = argparse.ArgumentParser(
48+
description="NiC simulator client"
49+
)
50+
parser.add_argument(
51+
"-s",
52+
"--server",
53+
required=True,
54+
help="gRPC server address"
55+
)
56+
parser.add_argument(
57+
"-p",
58+
"--server_port",
59+
required=True,
60+
help="gRPC server port"
61+
)
62+
parser.add_argument(
63+
"-m",
64+
"--test_mgmt",
65+
default=False,
66+
action="store_true",
67+
help="Test mgmt gRPC server"
68+
)
69+
return parser.parse_args()
70+
71+
72+
def main():
73+
args = parse_args()
74+
server = args.server
75+
port = args.server_port
76+
test_mgmt = args.test_mgmt
77+
with grpc.insecure_channel("%s:%s" % (server, port)) as channel:
78+
# metadata_interceptor = MetadataInterceptor(("grpc_server", "192.168.0.101"))
79+
# with grpc.intercept_channel(insecure_channel, metadata_interceptor) as channel:
80+
if test_mgmt:
81+
stub = nic_simulator_grpc_mgmt_service_pb2_grpc.DualTorMgmtServiceStub(channel)
82+
request = nic_simulator_grpc_mgmt_service_pb2.ListOfAdminRequest(
83+
nic_addresses=["192.168.0.3", "192.168.0.5"],
84+
admin_requests=[
85+
nic_simulator_grpc_service_pb2.AdminRequest(
86+
portid=[0, 1],
87+
state=[True, True]
88+
),
89+
nic_simulator_grpc_service_pb2.AdminRequest(
90+
portid=[0, 1],
91+
state=[True, True]
92+
),
93+
]
94+
)
95+
response = stub.QueryAdminPortState(request)
96+
print(response)
97+
98+
request = nic_simulator_grpc_mgmt_service_pb2.ListOfAdminRequest(
99+
nic_addresses=["192.168.0.3", "192.168.0.5"],
100+
admin_requests=[
101+
nic_simulator_grpc_service_pb2.AdminRequest(
102+
portid=[0, 1],
103+
state=[False, True]
104+
),
105+
nic_simulator_grpc_service_pb2.AdminRequest(
106+
portid=[0, 1],
107+
state=[True, False]
108+
),
109+
]
110+
)
111+
response = stub.SetAdminPortState(request)
112+
print(response)
113+
114+
request = nic_simulator_grpc_mgmt_service_pb2.ListOfAdminRequest(
115+
nic_addresses=["192.168.0.3", "192.168.0.5"],
116+
admin_requests=[
117+
nic_simulator_grpc_service_pb2.AdminRequest(
118+
portid=[0, 1],
119+
state=[True, True]
120+
),
121+
nic_simulator_grpc_service_pb2.AdminRequest(
122+
portid=[0, 1],
123+
state=[True, True]
124+
),
125+
]
126+
)
127+
response = stub.QueryAdminPortState(request)
128+
print(response)
129+
else:
130+
stub = nic_simulator_grpc_service_pb2_grpc.DualTorServiceStub(channel)
131+
request = nic_simulator_grpc_service_pb2.AdminRequest(
132+
portid=[0, 1],
133+
state=[True, True]
134+
)
135+
response = stub.QueryAdminPortState(request)
136+
print(response)
137+
138+
request = nic_simulator_grpc_service_pb2.AdminRequest(
139+
portid=[0, 1],
140+
state=[True, False]
141+
)
142+
response = stub.SetAdminPortState(request)
143+
print(response)
144+
145+
146+
if __name__ == "__main__":
147+
main()
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
syntax = "proto3";
2+
3+
import "nic_simulator_grpc_service.proto";
4+
5+
service DualTorMgmtService {
6+
rpc QueryAdminPortState(ListOfAdminRequest) returns (ListOfAdminReply) {}
7+
8+
rpc SetAdminPortState(ListOfAdminRequest) returns (ListOfAdminReply) {}
9+
10+
rpc QueryOperationPortState(ListOfOperationRequest) returns (ListOfOperationReply) {}
11+
}
12+
13+
message ListOfAdminRequest {
14+
repeated string nic_addresses = 1;
15+
repeated AdminRequest admin_requests = 2;
16+
}
17+
18+
message ListOfAdminReply {
19+
repeated string nic_addresses = 1;
20+
repeated AdminReply admin_replies = 2;
21+
}
22+
23+
message ListOfOperationRequest {
24+
repeated string nic_addresses = 1;
25+
repeated OperationRequest operation_requests = 2;
26+
}
27+
28+
message ListOfOperationReply {
29+
repeated string nic_addresses = 1;
30+
repeated OperationReply operation_replies = 2;
31+
}

ansible/dualtor/nic_simulator/nic_simulator_grpc_mgmt_service_pb2.py

Lines changed: 68 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
2+
"""Client and server classes corresponding to protobuf-defined services."""
3+
import grpc
4+
5+
import nic_simulator_grpc_mgmt_service_pb2 as nic__simulator__grpc__mgmt__service__pb2
6+
7+
8+
class DualTorMgmtServiceStub(object):
9+
"""Missing associated documentation comment in .proto file."""
10+
11+
def __init__(self, channel):
12+
"""Constructor.
13+
14+
Args:
15+
channel: A grpc.Channel.
16+
"""
17+
self.QueryAdminPortState = channel.unary_unary(
18+
'/DualTorMgmtService/QueryAdminPortState',
19+
request_serializer=nic__simulator__grpc__mgmt__service__pb2.ListOfAdminRequest.SerializeToString,
20+
response_deserializer=nic__simulator__grpc__mgmt__service__pb2.ListOfAdminReply.FromString,
21+
)
22+
self.SetAdminPortState = channel.unary_unary(
23+
'/DualTorMgmtService/SetAdminPortState',
24+
request_serializer=nic__simulator__grpc__mgmt__service__pb2.ListOfAdminRequest.SerializeToString,
25+
response_deserializer=nic__simulator__grpc__mgmt__service__pb2.ListOfAdminReply.FromString,
26+
)
27+
self.QueryOperationPortState = channel.unary_unary(
28+
'/DualTorMgmtService/QueryOperationPortState',
29+
request_serializer=nic__simulator__grpc__mgmt__service__pb2.ListOfOperationRequest.SerializeToString,
30+
response_deserializer=nic__simulator__grpc__mgmt__service__pb2.ListOfOperationReply.FromString,
31+
)
32+
33+
34+
class DualTorMgmtServiceServicer(object):
35+
"""Missing associated documentation comment in .proto file."""
36+
37+
def QueryAdminPortState(self, request, context):
38+
"""Missing associated documentation comment in .proto file."""
39+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
40+
context.set_details('Method not implemented!')
41+
raise NotImplementedError('Method not implemented!')
42+
43+
def SetAdminPortState(self, request, context):
44+
"""Missing associated documentation comment in .proto file."""
45+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
46+
context.set_details('Method not implemented!')
47+
raise NotImplementedError('Method not implemented!')
48+
49+
def QueryOperationPortState(self, request, context):
50+
"""Missing associated documentation comment in .proto file."""
51+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
52+
context.set_details('Method not implemented!')
53+
raise NotImplementedError('Method not implemented!')
54+
55+
56+
def add_DualTorMgmtServiceServicer_to_server(servicer, server):
57+
rpc_method_handlers = {
58+
'QueryAdminPortState': grpc.unary_unary_rpc_method_handler(
59+
servicer.QueryAdminPortState,
60+
request_deserializer=nic__simulator__grpc__mgmt__service__pb2.ListOfAdminRequest.FromString,
61+
response_serializer=nic__simulator__grpc__mgmt__service__pb2.ListOfAdminReply.SerializeToString,
62+
),
63+
'SetAdminPortState': grpc.unary_unary_rpc_method_handler(
64+
servicer.SetAdminPortState,
65+
request_deserializer=nic__simulator__grpc__mgmt__service__pb2.ListOfAdminRequest.FromString,
66+
response_serializer=nic__simulator__grpc__mgmt__service__pb2.ListOfAdminReply.SerializeToString,
67+
),
68+
'QueryOperationPortState': grpc.unary_unary_rpc_method_handler(
69+
servicer.QueryOperationPortState,
70+
request_deserializer=nic__simulator__grpc__mgmt__service__pb2.ListOfOperationRequest.FromString,
71+
response_serializer=nic__simulator__grpc__mgmt__service__pb2.ListOfOperationReply.SerializeToString,
72+
),
73+
}
74+
generic_handler = grpc.method_handlers_generic_handler(
75+
'DualTorMgmtService', rpc_method_handlers)
76+
server.add_generic_rpc_handlers((generic_handler,))
77+
78+
79+
# This class is part of an EXPERIMENTAL API.
80+
class DualTorMgmtService(object):
81+
"""Missing associated documentation comment in .proto file."""
82+
83+
@staticmethod
84+
def QueryAdminPortState(request,
85+
target,
86+
options=(),
87+
channel_credentials=None,
88+
call_credentials=None,
89+
insecure=False,
90+
compression=None,
91+
wait_for_ready=None,
92+
timeout=None,
93+
metadata=None):
94+
return grpc.experimental.unary_unary(request, target, '/DualTorMgmtService/QueryAdminPortState',
95+
nic__simulator__grpc__mgmt__service__pb2.ListOfAdminRequest.SerializeToString,
96+
nic__simulator__grpc__mgmt__service__pb2.ListOfAdminReply.FromString,
97+
options, channel_credentials,
98+
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
99+
100+
@staticmethod
101+
def SetAdminPortState(request,
102+
target,
103+
options=(),
104+
channel_credentials=None,
105+
call_credentials=None,
106+
insecure=False,
107+
compression=None,
108+
wait_for_ready=None,
109+
timeout=None,
110+
metadata=None):
111+
return grpc.experimental.unary_unary(request, target, '/DualTorMgmtService/SetAdminPortState',
112+
nic__simulator__grpc__mgmt__service__pb2.ListOfAdminRequest.SerializeToString,
113+
nic__simulator__grpc__mgmt__service__pb2.ListOfAdminReply.FromString,
114+
options, channel_credentials,
115+
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
116+
117+
@staticmethod
118+
def QueryOperationPortState(request,
119+
target,
120+
options=(),
121+
channel_credentials=None,
122+
call_credentials=None,
123+
insecure=False,
124+
compression=None,
125+
wait_for_ready=None,
126+
timeout=None,
127+
metadata=None):
128+
return grpc.experimental.unary_unary(request, target, '/DualTorMgmtService/QueryOperationPortState',
129+
nic__simulator__grpc__mgmt__service__pb2.ListOfOperationRequest.SerializeToString,
130+
nic__simulator__grpc__mgmt__service__pb2.ListOfOperationReply.FromString,
131+
options, channel_credentials,
132+
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)

0 commit comments

Comments
 (0)