@@ -6,32 +6,51 @@ import (
6
6
"fmt"
7
7
"time"
8
8
9
+ "github.com/go-logr/logr"
9
10
pb "github.com/nginx/agent/v3/api/grpc/mpi/v1"
10
11
"google.golang.org/grpc"
12
+
13
+ agentgrpc "github.com/nginx/nginx-gateway-fabric/internal/mode/static/nginx/agent/grpc"
14
+ grpcContext "github.com/nginx/nginx-gateway-fabric/internal/mode/static/nginx/agent/grpc/context"
11
15
)
12
16
13
- // commandService handles the connection and subscription to the agent.
17
+ // commandService handles the connection and subscription to the data plane agent.
14
18
type commandService struct {
15
19
pb.CommandServiceServer
20
+ connTracker * agentgrpc.ConnectionsTracker
21
+ // TODO(sberman): all logs are at Info level right now. Adjust appropriately.
22
+ logger logr.Logger
16
23
}
17
24
18
- func newCommandService () * commandService {
19
- return & commandService {}
25
+ func newCommandService (logger logr.Logger ) * commandService {
26
+ return & commandService {
27
+ logger : logger ,
28
+ connTracker : agentgrpc .NewConnectionsTracker (),
29
+ }
20
30
}
21
31
22
32
func (cs * commandService ) Register (server * grpc.Server ) {
23
33
pb .RegisterCommandServiceServer (server , cs )
24
34
}
25
35
36
+ // CreateConnection registers a data plane agent with the control plane.
26
37
func (cs * commandService ) CreateConnection (
27
- _ context.Context ,
38
+ ctx context.Context ,
28
39
req * pb.CreateConnectionRequest ,
29
40
) (* pb.CreateConnectionResponse , error ) {
30
41
if req == nil {
31
42
return nil , errors .New ("empty connection request" )
32
43
}
33
44
34
- fmt .Printf ("Creating connection for nginx pod: %s\n " , req .GetResource ().GetContainerInfo ().GetHostname ())
45
+ gi , ok := grpcContext .GrpcInfoFromContext (ctx )
46
+ if ! ok {
47
+ return nil , agentgrpc .ErrStatusInvalidConnection
48
+ }
49
+
50
+ podName := req .GetResource ().GetContainerInfo ().GetHostname ()
51
+
52
+ cs .logger .Info (fmt .Sprintf ("Creating connection for nginx pod: %s" , podName ))
53
+ cs .connTracker .Track (gi .IPAddress , podName )
35
54
36
55
return & pb.CreateConnectionResponse {
37
56
Response : & pb.CommandResponse {
@@ -40,50 +59,99 @@ func (cs *commandService) CreateConnection(
40
59
}, nil
41
60
}
42
61
62
+ // Subscribe is a decoupled communication mechanism between the data plane agent and control plane.
43
63
func (cs * commandService ) Subscribe (in pb.CommandService_SubscribeServer ) error {
44
- fmt .Println ("Received subscribe request" )
45
-
46
64
ctx := in .Context ()
47
65
66
+ gi , ok := grpcContext .GrpcInfoFromContext (ctx )
67
+ if ! ok {
68
+ return agentgrpc .ErrStatusInvalidConnection
69
+ }
70
+
71
+ cs .logger .Info (fmt .Sprintf ("Received subscribe request from %q" , gi .IPAddress ))
72
+
73
+ go cs .listenForDataPlaneResponse (ctx , in )
74
+
75
+ // wait for the agent to report itself
76
+ podName , err := cs .waitForConnection (ctx , gi )
77
+ if err != nil {
78
+ cs .logger .Error (err , "error waiting for connection" )
79
+ return err
80
+ }
81
+
82
+ cs .logger .Info (fmt .Sprintf ("Handling subscription for %s/%s" , podName , gi .IPAddress ))
48
83
for {
49
84
select {
50
85
case <- ctx .Done ():
51
86
return ctx .Err ()
52
87
case <- time .After (1 * time .Minute ):
53
88
dummyRequest := & pb.ManagementPlaneRequest {
54
- Request : & pb.ManagementPlaneRequest_StatusRequest {
55
- StatusRequest : & pb.StatusRequest {},
89
+ Request : & pb.ManagementPlaneRequest_HealthRequest {
90
+ HealthRequest : & pb.HealthRequest {},
56
91
},
57
92
}
58
- if err := in .Send (dummyRequest ); err != nil { // will likely need retry logic
59
- fmt . Printf ( "ERROR: %v \n " , err )
93
+ if err := in .Send (dummyRequest ); err != nil { // TODO(sberman): will likely need retry logic
94
+ cs . logger . Error ( err , "error sending request to agent" )
60
95
}
61
96
}
62
97
}
63
98
}
64
99
65
- func (cs * commandService ) UpdateDataPlaneStatus (
66
- _ context.Context ,
67
- req * pb.UpdateDataPlaneStatusRequest ,
68
- ) (* pb.UpdateDataPlaneStatusResponse , error ) {
69
- fmt .Println ("Updating data plane status" )
100
+ // TODO(sberman): current issue: when control plane restarts, agent doesn't re-establish a CreateConnection call,
101
+ // so this fails.
102
+ func (cs * commandService ) waitForConnection (ctx context.Context , gi grpcContext.GrpcInfo ) (string , error ) {
103
+ var podName string
104
+ ticker := time .NewTicker (time .Second )
105
+ defer ticker .Stop ()
70
106
71
- if req == nil {
72
- return nil , errors .New ("empty update data plane status request" )
107
+ timer := time .NewTimer (30 * time .Second )
108
+ defer timer .Stop ()
109
+
110
+ for {
111
+ select {
112
+ case <- ctx .Done ():
113
+ return "" , ctx .Err ()
114
+ case <- timer .C :
115
+ return "" , errors .New ("timed out waiting for agent connection" )
116
+ case <- ticker .C :
117
+ if podName = cs .connTracker .GetConnection (gi .IPAddress ); podName != "" {
118
+ return podName , nil
119
+ }
120
+ }
73
121
}
122
+ }
74
123
75
- return & pb.UpdateDataPlaneStatusResponse {}, nil
124
+ func (cs * commandService ) listenForDataPlaneResponse (ctx context.Context , in pb.CommandService_SubscribeServer ) {
125
+ for {
126
+ select {
127
+ case <- ctx .Done ():
128
+ return
129
+ default :
130
+ dataPlaneResponse , err := in .Recv ()
131
+ cs .logger .Info (fmt .Sprintf ("Received data plane response: %v" , dataPlaneResponse ))
132
+ if err != nil {
133
+ cs .logger .Error (err , "failed to receive data plane response" )
134
+ return
135
+ }
136
+ }
137
+ }
76
138
}
77
139
140
+ // UpdateDataPlaneHealth includes full health information about the data plane as reported by the agent.
141
+ // TODO(sberman): Is health monitoring the data planes something useful for us to do?
78
142
func (cs * commandService ) UpdateDataPlaneHealth (
79
143
_ context.Context ,
80
- req * pb.UpdateDataPlaneHealthRequest ,
144
+ _ * pb.UpdateDataPlaneHealthRequest ,
81
145
) (* pb.UpdateDataPlaneHealthResponse , error ) {
82
- fmt .Println ("Updating data plane health" )
83
-
84
- if req == nil {
85
- return nil , errors .New ("empty update dataplane health request" )
86
- }
87
-
88
146
return & pb.UpdateDataPlaneHealthResponse {}, nil
89
147
}
148
+
149
+ // UpdateDataPlaneStatus is called by agent on startup and upon any change in agent metadata,
150
+ // instance metadata, or configurations. Since directly changing nginx configuration on the instance
151
+ // is not supported, this is a no-op for NGF.
152
+ func (cs * commandService ) UpdateDataPlaneStatus (
153
+ _ context.Context ,
154
+ _ * pb.UpdateDataPlaneStatusRequest ,
155
+ ) (* pb.UpdateDataPlaneStatusResponse , error ) {
156
+ return & pb.UpdateDataPlaneStatusResponse {}, nil
157
+ }
0 commit comments