@@ -1151,6 +1151,174 @@ service:
11511151
11521152}
11531153
1154+ func TestCoordinatorManagesComponentWorkDirs (t * testing.T ) {
1155+ // Send a test policy to the Coordinator as a Config Manager update,
1156+ // verify it creates a working directory for the component, keeps that working directory as the component
1157+ // moves to a different runtime, then deletes it after the component is stopped.
1158+ top := paths .Top ()
1159+ paths .SetTop (t .TempDir ())
1160+ t .Cleanup (func () {
1161+ paths .SetTop (top )
1162+ })
1163+
1164+ ctx , cancel := context .WithTimeout (context .Background (), time .Second )
1165+ defer cancel ()
1166+ logger := logp .NewLogger ("testing" )
1167+
1168+ configChan := make (chan ConfigChange , 1 )
1169+ updateChan := make (chan runtime.ComponentComponentState , 1 )
1170+
1171+ // Create a mocked runtime manager that will report the update call
1172+ runtimeManager := & fakeRuntimeManager {}
1173+ otelManager := & fakeOTelManager {}
1174+
1175+ // we need the filestream spec to be able to convert to Otel config
1176+ componentSpec := component.InputRuntimeSpec {
1177+ InputType : "filestream" ,
1178+ BinaryName : "agentbeat" ,
1179+ Spec : component.InputSpec {
1180+ Name : "filestream" ,
1181+ Command : & component.CommandSpec {
1182+ Args : []string {"filebeat" },
1183+ },
1184+ Platforms : []string {
1185+ "linux/amd64" ,
1186+ "linux/arm64" ,
1187+ "darwin/amd64" ,
1188+ "darwin/arm64" ,
1189+ "windows/amd64" ,
1190+ "container/amd64" ,
1191+ "container/arm64" ,
1192+ },
1193+ },
1194+ }
1195+
1196+ platform , err := component .LoadPlatformDetail ()
1197+ require .NoError (t , err )
1198+ specs , err := component .NewRuntimeSpecs (platform , []component.InputRuntimeSpec {componentSpec })
1199+ require .NoError (t , err )
1200+
1201+ monitoringMgr := newTestMonitoringMgr ()
1202+ coord := & Coordinator {
1203+ logger : logger ,
1204+ agentInfo : & info.AgentInfo {},
1205+ stateBroadcaster : broadcaster .New (State {}, 0 , 0 ),
1206+ managerChans : managerChans {
1207+ configManagerUpdate : configChan ,
1208+ runtimeManagerUpdate : updateChan ,
1209+ },
1210+ monitorMgr : monitoringMgr ,
1211+ runtimeMgr : runtimeManager ,
1212+ otelMgr : otelManager ,
1213+ specs : specs ,
1214+ vars : emptyVars (t ),
1215+ componentPIDTicker : time .NewTicker (time .Second * 30 ),
1216+ secretMarkerFunc : testSecretMarkerFunc ,
1217+ }
1218+
1219+ var workDirPath string
1220+ var workDirCreated time.Time
1221+
1222+ t .Run ("run in process manager" , func (t * testing.T ) {
1223+ // Create a policy with one input and one output (no otel configuration)
1224+ cfg := config .MustNewConfigFrom (`
1225+ outputs:
1226+ default:
1227+ type: elasticsearch
1228+ hosts:
1229+ - localhost:9200
1230+ inputs:
1231+ - id: test-input
1232+ type: filestream
1233+ use_output: default
1234+ _runtime_experimental: process
1235+ ` )
1236+
1237+ // Send the policy change and make sure it was acknowledged.
1238+ cfgChange := & configChange {cfg : cfg }
1239+ configChan <- cfgChange
1240+ coord .runLoopIteration (ctx )
1241+ assert .True (t , cfgChange .acked , "Coordinator should ACK a successful policy change" )
1242+ assert .NoError (t , cfgChange .err , "config processing shouldn't report an error" )
1243+ require .Len (t , coord .componentModel , 1 , "there should be one component" )
1244+ workDirPath = coord .componentModel [0 ].WorkDirPath (paths .Run ())
1245+ stat , err := os .Stat (workDirPath )
1246+ require .NoError (t , err , "component working directory should exist" )
1247+ assert .True (t , stat .IsDir (), "component working directory should exist" )
1248+ workDirCreated = stat .ModTime ()
1249+ })
1250+
1251+ t .Run ("run in otel manager" , func (t * testing.T ) {
1252+ // Create a policy with one input and one output (no otel configuration)
1253+ cfg := config .MustNewConfigFrom (`
1254+ outputs:
1255+ default:
1256+ type: elasticsearch
1257+ hosts:
1258+ - localhost:9200
1259+ inputs:
1260+ - id: test-input
1261+ type: filestream
1262+ use_output: default
1263+ _runtime_experimental: otel
1264+ ` )
1265+
1266+ // Send the policy change and make sure it was acknowledged.
1267+ cfgChange := & configChange {cfg : cfg }
1268+ configChan <- cfgChange
1269+ coord .runLoopIteration (ctx )
1270+ assert .True (t , cfgChange .acked , "Coordinator should ACK a successful policy change" )
1271+ assert .NoError (t , cfgChange .err , "config processing shouldn't report an error" )
1272+ require .Len (t , coord .componentModel , 1 , "there should be one component" )
1273+ compState := runtime.ComponentComponentState {
1274+ Component : component.Component {
1275+ ID : "filestream-default" ,
1276+ },
1277+ State : runtime.ComponentState {
1278+ State : client .UnitStateStopped ,
1279+ },
1280+ }
1281+ updateChan <- compState
1282+ coord .runLoopIteration (ctx )
1283+ stat , err := os .Stat (workDirPath )
1284+ require .NoError (t , err , "component working directory should exist" )
1285+ assert .True (t , stat .IsDir (), "component working directory should exist" )
1286+ assert .Equal (t , workDirCreated , stat .ModTime (), "component working directory shouldn't have been modified" )
1287+ })
1288+ t .Run ("remove component" , func (t * testing.T ) {
1289+ // Create a policy with one input and one output (no otel configuration)
1290+ cfg := config .MustNewConfigFrom (`
1291+ outputs:
1292+ default:
1293+ type: elasticsearch
1294+ hosts:
1295+ - localhost:9200
1296+ inputs: []
1297+ ` )
1298+
1299+ // Send the policy change and make sure it was acknowledged.
1300+ cfgChange := & configChange {cfg : cfg }
1301+ configChan <- cfgChange
1302+ coord .runLoopIteration (ctx )
1303+ assert .True (t , cfgChange .acked , "Coordinator should ACK a successful policy change" )
1304+ assert .NoError (t , cfgChange .err , "config processing shouldn't report an error" )
1305+ require .Len (t , coord .componentModel , 0 , "there should be one component" )
1306+
1307+ compState := runtime.ComponentComponentState {
1308+ Component : component.Component {
1309+ ID : "filestream-default" ,
1310+ },
1311+ State : runtime.ComponentState {
1312+ State : client .UnitStateStopped ,
1313+ },
1314+ }
1315+ updateChan <- compState
1316+ coord .runLoopIteration (ctx )
1317+ assert .NoDirExists (t , workDirPath , "component working directory shouldn't exist anymore" )
1318+ })
1319+
1320+ }
1321+
11541322func TestCoordinatorReportsRuntimeManagerUpdateFailure (t * testing.T ) {
11551323 // Set a one-second timeout -- nothing here should block, but if it
11561324 // does let's report a failure instead of timing out the test runner.
0 commit comments