Skip to content

Commit 4334fe2

Browse files
fix stopScenarioDocker & API update
1 parent 2a3e524 commit 4334fe2

3 files changed

Lines changed: 49 additions & 90 deletions

File tree

nebula/controller/federation/controllers/docker_federation_controller.py

Lines changed: 42 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from nebula.controller.federation.utils_requests import factory_requests_path
1111
from nebula.controller.federation.utils_requests import NodeUpdateRequest, NodeDoneRequest
1212
from typing import Dict
13-
from fastapi import Request
1413
from nebula.config.config import Config
1514
from nebula.core.utils.certificate import generate_ca_certificate
1615
from nebula.core.utils.locker import Locker
@@ -113,93 +112,50 @@ async def stop_scenario(self, federation_id: str):
113112
Reads ALL scenario.metadata and removes all listed containers and the network, then deletes the metadata file.
114113
Also forcibly stops and removes any containers still attached to the network before removing it.
115114
"""
116-
federation_scenario_name = await self._remove_nebula_federation_from_pool(federation_id)
117-
if not federation_scenario_name:
115+
federation = await self._remove_nebula_federation_from_pool(federation_id)
116+
if not federation:
118117
return False
119118

120-
# Try multiple possible config directory locations. This depends on where the user called the function from.
121-
possible_config_dirs = [
122-
os.environ.get("NEBULA_CONFIG_DIR"),
123-
"/nebula/app/config",
124-
"./app/config",
125-
os.path.join(os.getcwd(), "app", "config"),
126-
os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "app", "config"),
127-
]
128-
129-
config_dir = None
130-
for dir_path in possible_config_dirs:
131-
if dir_path and os.path.exists(dir_path):
132-
config_dir = dir_path
133-
break
134-
135-
if not config_dir:
136-
self.logger.info("No valid config directory found, skipping cleanup")
137-
return
138-
139-
scenario_dirs = []
140-
self.logger.info(f"Config directory: {config_dir}")
141-
if os.path.exists(config_dir):
142-
for item in os.listdir(config_dir):
143-
scenario_path = os.path.join(config_dir, item)
144-
if os.path.isdir(scenario_path):
145-
metadata_file = os.path.join(scenario_path, "scenario.metadata")
146-
if os.path.exists(metadata_file):
147-
scenario_dirs.append(scenario_path)
148-
149-
self.logger.info(f"Removing scenario containers for {scenario_dirs}")
150-
if not scenario_dirs:
151-
self.logger.info("No active scenarios found to clean up")
152-
return
153-
154119
client = docker.from_env()
155-
156-
for scenario_dir in scenario_dirs:
157-
if scenario_dir != federation_scenario_name:
158-
continue
159-
160-
metadata_path = os.path.join(scenario_dir, "scenario.metadata")
161-
if not os.path.exists(metadata_path):
162-
self.logger.info(f"Skipping {scenario_dir} - no scenario.metadata found")
163-
continue
164-
165-
with open(metadata_path) as f:
166-
meta = json.load(f)
167-
168-
# Remove containers listed in metadata
169-
for name in meta.get("containers", []):
170-
try:
171-
container = client.containers.get(name)
172-
container.remove(force=True)
173-
self.logger.info(f"Removed scenario container {name}")
174-
except Exception as e:
175-
self.logger.info(f"Could not remove scenario container {name}: {e}")
176-
177-
# Remove network, but first forcibly remove any containers still attached
178-
network_name = meta.get("network")
179-
if network_name:
180-
try:
181-
network = client.networks.get(network_name)
182-
attached_containers = network.attrs.get("Containers") or {}
183-
for container_id in attached_containers:
184-
try:
185-
c = client.containers.get(container_id)
186-
c.remove(force=True)
187-
self.logger.info(f"Force-removed container {c.name} attached to {network_name}")
188-
except Exception as e:
189-
self.logger.info(f"Could not force-remove container {container_id}: {e}")
190-
network.remove()
191-
self.logger.info(f"Removed scenario network {network_name}")
192-
except Exception as e:
193-
self.logger.info(f"Could not remove scenario network {network_name}: {e}")
194-
195-
# Remove metadata file
120+
metadata_path = os.path.join(federation.config_dir, "scenario.metadata")
121+
122+
if not os.path.exists(metadata_path):
123+
self.logger.info(f"ERROR {metadata_path} - no 'scenario.metadata' found")
124+
return False
125+
126+
with open(metadata_path) as f:
127+
meta = json.load(f)
128+
# Remove containers listed in metadata
129+
for name in meta.get("containers", []):
196130
try:
197-
os.remove(metadata_path)
131+
container = client.containers.get(name)
132+
container.remove(force=True)
133+
self.logger.info(f"Removed scenario container {name}")
198134
except Exception as e:
199-
self.logger.info(f"Could not remove scenario.metadata: {e}")
200-
201-
if scenario_dir == federation_scenario_name:
202-
break
135+
self.logger.info(f"Could not remove scenario container {name}: {e}")
136+
# Remove network, but first forcibly remove any containers still attached
137+
network_name = meta.get("network")
138+
if network_name:
139+
try:
140+
network = client.networks.get(network_name)
141+
attached_containers = network.attrs.get("Containers") or {}
142+
for container_id in attached_containers:
143+
try:
144+
c = client.containers.get(container_id)
145+
c.remove(force=True)
146+
self.logger.info(f"Force-removed container {c.name} attached to {network_name}")
147+
except Exception as e:
148+
self.logger.info(f"Could not force-remove container {container_id}: {e}")
149+
network.remove()
150+
self.logger.info(f"Removed scenario network {network_name}")
151+
except Exception as e:
152+
self.logger.info(f"Could not remove scenario network {network_name}: {e}")
153+
# Remove metadata file
154+
try:
155+
os.remove(metadata_path)
156+
except Exception as e:
157+
self.logger.info(f"Could not remove scenario.metadata: {e}")
158+
return False
203159

204160
return True #TODO care about cases
205161

@@ -269,15 +225,15 @@ async def _add_nebula_federation_to_pool(self, federation_id: str, user: str):
269225
self.logger.info(f"ERROR: trying to add ({federation_id}) to federations pool..")
270226
return fed
271227

272-
async def _remove_nebula_federation_from_pool(self, federation_id: str):
228+
async def _remove_nebula_federation_from_pool(self, federation_id: str) -> NebulaFederationDocker | None:
273229
async with self._federations_dict_lock:
274230
if federation_id in self.nfp:
275231
federation = self.nfp.pop(federation_id)
276232
self.logger.info(f"SUCCESS: Federation ID: ({federation_id}) removed from pool")
277-
return federation.scenario_name
233+
return federation
278234
else:
279235
self.logger.info(f"ERROR: trying to remove ({federation_id}) from federations pool..")
280-
return ""
236+
return None
281237

282238
async def _update_federation_on_pool(self, federation_id: str, user: str, nf: NebulaFederationDocker):
283239
updated = False

nebula/controller/federation/federation_api.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,17 @@ async def run_scenario(run_scenario_request: RunScenarioRequest):
6464
return {"message": "Experiment type not allowed"}
6565

6666
@app.post(Routes.STOP)
67-
async def stop_scenario(stop_scenario_request: StopScenarioRequest):
67+
async def stop_scenario(
68+
federation_id: str,
69+
stop_scenario_request: StopScenarioRequest
70+
):
6871
global fed_controllers
6972
experiment_type = stop_scenario_request.experiment_type
7073
controller = fed_controllers.get(experiment_type, None)
7174
logger = logging.getLogger("Federation-Controller")
7275
logger.info(f"[API]: stop experiment request for federation ID: {stop_scenario_request.federation_id}")
7376
if controller:
74-
return await controller.stop_scenario(stop_scenario_request.federation_id)
77+
return await controller.stop_scenario(federation_id)
7578
else:
7679
return {"message": "Experiment type not allowed"}
7780

nebula/controller/federation/utils_requests.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class NodeDoneRequest(BaseModel):
2222
class Routes:
2323
INIT = "/init"
2424
RUN = "/scenarios/run"
25-
STOP = "/scenarios/stop"
25+
STOP = "/scenarios/{federation_id}/stop"
2626
UPDATE = "/nodes/{federation_id}/update"
2727
DONE = "/nodes/{federation_id}/done"
2828
FINISH = "/scenarios/{federation_id}/finish"
@@ -33,7 +33,7 @@ def factory_requests_path(resource: str, scenario_name: str = "", federation_id:
3333
elif resource == "run":
3434
return Routes.RUN
3535
elif resource == "stop":
36-
return Routes.STOP
36+
return Routes.STOP.format(federation_id=federation_id)
3737
elif resource == "update":
3838
return Routes.UPDATE.format(federation_id=federation_id)
3939
elif resource == "done":

0 commit comments

Comments
 (0)