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