11// SPDX-License-Identifier: Apache-2.0
22// Copyright Open Network Fabric Authors
33
4+ use chrono:: { TimeZone , Utc } ;
5+ use config:: converters:: k8s:: ToK8sConversionError ;
46use tokio:: sync:: mpsc:: Sender ;
57
6- use config:: { ExternalConfig , GwConfig } ;
7- use k8s_intf:: client:: WatchError ;
8- use k8s_intf:: watch_gateway_agent_crd;
8+ use config:: converters:: k8s:: status:: dataplane_status:: DataplaneStatusForK8sConversion ;
9+ use config:: { ExternalConfig , GwConfig , internal:: status:: DataplaneStatus } ;
10+ use k8s_intf:: client:: { PatchError , WatchError , patch_gateway_status, watch_gateway_agent_crd} ;
11+ use k8s_intf:: gateway_agent_crd:: GatewayAgentStatus ;
912use tracing:: error;
1013
1114use crate :: processor:: proc:: { ConfigChannelRequest , ConfigRequest , ConfigResponse } ;
@@ -14,43 +17,161 @@ use crate::processor::proc::{ConfigChannelRequest, ConfigRequest, ConfigResponse
1417pub enum K8sClientError {
1518 #[ error( "K8s client exited early" ) ]
1619 EarlyTermination ,
17- #[ error( "K8s client could not get hostname: {0}" ) ]
18- HostnameError ( #[ from] std:: io:: Error ) ,
1920 #[ error( "K8s watch failed: {0}" ) ]
2021 WatchError ( #[ from] WatchError ) ,
22+ #[ error( "Failed to convert dataplane status to k8s format: {0}" ) ]
23+ StatusConversionError ( #[ from] ToK8sConversionError ) ,
24+ #[ error( "Failed to patch k8s gateway status: {0}" ) ]
25+ PatchStatusError ( #[ from] PatchError ) ,
2126}
2227
23- pub async fn k8s_start_client (
24- hostname : & str ,
25- tx : Sender < ConfigChannelRequest > ,
26- ) -> Result < ( ) , K8sClientError > {
27- watch_gateway_agent_crd ( hostname, async move |ga| {
28- let external_config = ExternalConfig :: try_from ( ga) ;
29- match external_config {
30- Ok ( external_config) => {
31- let gw_config = Box :: new ( GwConfig :: new ( external_config) ) ;
32-
33- let ( req, rx) = ConfigChannelRequest :: new ( ConfigRequest :: ApplyConfig ( gw_config) ) ;
34- let tx_result = tx. send ( req) . await ;
35- if let Err ( e) = tx_result {
36- error ! ( "Failure sending request to config processor: {e}" ) ;
28+ async fn get_dataplane_status (
29+ tx : & Sender < ConfigChannelRequest > ,
30+ ) -> Result < DataplaneStatus , MgmtStatusError > {
31+ let ( req, rx) = ConfigChannelRequest :: new ( ConfigRequest :: GetDataplaneStatus ) ;
32+ tx. send ( req) . await . map_err ( |_| {
33+ MgmtStatusError :: FetchStatusError ( "Failure relaying status fetch request" . to_string ( ) )
34+ } ) ?;
35+ let response = rx. await . map_err ( |_| {
36+ MgmtStatusError :: FetchStatusError (
37+ "Failure receiving status from config processor" . to_string ( ) ,
38+ )
39+ } ) ?;
40+
41+ match response {
42+ ConfigResponse :: GetDataplaneStatus ( status) => Ok ( * status) ,
43+ _ => unreachable ! ( ) ,
44+ }
45+ }
46+
47+ #[ derive( Debug , thiserror:: Error ) ]
48+ enum MgmtStatusError {
49+ #[ error( "Failed to fetch dataplane status: {0}" ) ]
50+ FetchStatusError ( String ) ,
51+ }
52+
53+ pub struct K8sClient {
54+ hostname : String ,
55+ }
56+
57+ impl K8sClient {
58+ pub fn new ( hostname : & str ) -> Self {
59+ Self {
60+ hostname : hostname. to_string ( ) ,
61+ }
62+ }
63+
64+ pub async fn init ( & self ) -> Result < ( ) , K8sClientError > {
65+ // Reset the config generation and applied time in K8s
66+ patch_gateway_status (
67+ & self . hostname ,
68+ & GatewayAgentStatus {
69+ agent_version : Some ( "(none: agentless)" . to_string ( ) ) ,
70+ last_applied_gen : Some ( 0 ) ,
71+ last_applied_time : Some (
72+ Utc . timestamp_opt ( 0 , 0 )
73+ . unwrap ( )
74+ . to_rfc3339_opts ( chrono:: SecondsFormat :: Nanos , true ) ,
75+ ) ,
76+ state : None ,
77+ } ,
78+ )
79+ . await ?;
80+ Ok ( ( ) )
81+ }
82+
83+ pub async fn k8s_start_config_watch (
84+ & self ,
85+ tx : Sender < ConfigChannelRequest > ,
86+ ) -> Result < ( ) , K8sClientError > {
87+ // Clone this here so that the closure does not try to borrow self
88+ // and cause K8sClient to not be Send for 'static but only a specific
89+ // lifetime
90+ let hostname = self . hostname . clone ( ) ;
91+ watch_gateway_agent_crd ( & hostname. clone ( ) , async move |ga| {
92+ let external_config = ExternalConfig :: try_from ( ga) ;
93+ match external_config {
94+ Ok ( external_config) => {
95+ let genid = external_config. genid ;
96+ let gw_config = Box :: new ( GwConfig :: new ( external_config) ) ;
97+
98+ let ( req, rx) =
99+ ConfigChannelRequest :: new ( ConfigRequest :: ApplyConfig ( gw_config) ) ;
100+ let tx_result = tx. send ( req) . await ;
101+ if let Err ( e) = tx_result {
102+ error ! ( "Failure sending request to config processor: {e}" ) ;
103+ }
104+ match rx. await {
105+ Err ( e) => error ! ( "Failure receiving from config processor: {e}" ) ,
106+ Ok ( response) => match response {
107+ ConfigResponse :: ApplyConfig ( Err ( e) ) => {
108+ error ! ( "Failed to apply config: {e}" ) ;
109+ }
110+ ConfigResponse :: ApplyConfig ( Ok ( ( ) ) ) => {
111+ let last_applied_time = Some ( chrono:: Utc :: now ( ) ) ;
112+ let k8s_status = match GatewayAgentStatus :: try_from (
113+ & DataplaneStatusForK8sConversion {
114+ last_applied_gen : Some ( genid) ,
115+ last_applied_time : last_applied_time. as_ref ( ) ,
116+ last_collected_time : None ,
117+ status : None ,
118+ } ,
119+ ) {
120+ Ok ( v) => Some ( v) ,
121+ Err ( e) => { error ! ( "Unable to build object to patch k8s status with applied generation: {e}" ) ; None }
122+
123+ } ;
124+
125+ if let Some ( k8s_status) = k8s_status {
126+ match patch_gateway_status ( & hostname, & k8s_status) . await {
127+ Ok ( ( ) ) => { } ,
128+ Err ( e) => { error ! ( "Unable to patch k8s last_applied_gen and timestamp: {e}" ) ; }
129+ }
130+ }
131+ }
132+ _ => unreachable ! ( ) ,
133+ } ,
134+ } ;
135+ }
136+ Err ( e) => {
137+ error ! ( "Failed to convert K8sGatewayAgent to ExternalConfig: {e}" ) ;
37138 }
38- match rx. await {
39- Err ( e) => error ! ( "Failure receiving from config processor: {e}" ) ,
40- Ok ( response) => match response {
41- ConfigResponse :: ApplyConfig ( Err ( e) ) => {
42- error ! ( "Failed to apply config: {e}" ) ;
43- }
44- ConfigResponse :: ApplyConfig ( Ok ( ( ) ) ) => { }
45- _ => unreachable ! ( ) ,
46- } ,
47- } ;
48- }
49- Err ( e) => {
50- error ! ( "Failed to convert K8sGatewayAgent to ExternalConfig: {e}" ) ;
51139 }
140+ } )
141+ . await ?;
142+ Err ( K8sClientError :: EarlyTermination )
143+ }
144+
145+ pub async fn k8s_start_status_update (
146+ & self ,
147+ tx : Sender < ConfigChannelRequest > ,
148+ status_update_interval : & std:: time:: Duration ,
149+ ) -> Result < ( ) , K8sClientError > {
150+ // Clone this here so that the closure does not try to borrow self
151+ // and cause K8sClient to not be Send for 'static but only a specific
152+ // lifetime
153+ let hostname = self . hostname . clone ( ) ;
154+ loop {
155+ let status = get_dataplane_status ( & tx) . await ;
156+
157+ let status = match status {
158+ Ok ( status) => status,
159+ Err ( err) => {
160+ error ! ( "Failed to fetch dataplane status: {}" , err) ;
161+ continue ;
162+ }
163+ } ;
164+
165+ let k8s_status = GatewayAgentStatus :: try_from ( & DataplaneStatusForK8sConversion {
166+ last_applied_gen : None ,
167+ last_applied_time : None ,
168+ last_collected_time : Some ( & chrono:: Utc :: now ( ) ) ,
169+ status : Some ( & status) ,
170+ } ) ?;
171+ patch_gateway_status ( & hostname, & k8s_status) . await ?;
172+
173+ // Process status update
174+ tokio:: time:: sleep ( * status_update_interval) . await ;
52175 }
53- } )
54- . await ?;
55- Err ( K8sClientError :: EarlyTermination )
176+ }
56177}
0 commit comments