Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 46 additions & 13 deletions extensions/business/deeploy/deeploy_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2442,15 +2442,21 @@ def _aggregate_container_resources(self, inputs):
return legacy_resources

self.Pd(f"Processing {len(plugins_array)} plugin instances from plugins array")
total_cpu = Decimal("0")
total_memory_mb = 0
total_storage_mb = 0
has_storage_resource = False
resource_footprints_by_key = {}
used_instance_ids = set()

# Iterate through plugins array (simplified format - each object is an instance)
for idx, plugin_instance in enumerate(plugins_array):
signature = plugin_instance.get(DEEPLOY_KEYS.PLUGIN_SIGNATURE, "").upper()
self.Pd(f"Plugin {idx}: signature={signature}")
instance_id = plugin_instance.get(DEEPLOY_KEYS.PLUGIN_INSTANCE_ID) or plugin_instance.get(ct.CONFIG_INSTANCE.K_INSTANCE_ID)
if instance_id:
instance_id = str(instance_id)
if instance_id in used_instance_ids:
raise ValueError(
f"{DEEPLOY_ERRORS.PLUGINS3}: Duplicate plugin_instance_id '{instance_id}' in plugins array."
)
used_instance_ids.add(instance_id)

# Only aggregate for CONTAINER_APP_RUNNER and WORKER_APP_RUNNER plugins
if signature in CONTAINERIZED_APPS_SIGNATURES:
Expand All @@ -2460,26 +2466,53 @@ def _aggregate_container_resources(self, inputs):
container_storage = resources.get(DEEPLOY_RESOURCES.STORAGE)

self.Pd(f" Container resources: cpu={cpu}, memory={memory}")

total_cpu += cpu
memory_mb = parse_memory_to_mb(memory)
self.Pd(f" Parsed memory: {memory_mb}MB")
total_memory_mb += memory_mb

storage_mb = 0
has_storage_resource = False
if DEEPLOY_RESOURCES.STORAGE in resources:
has_storage_resource = True
storage_mb = self._parse_stack_storage_mb(container_storage, context=f"plugin {idx}")
self.Pd(f" Container storage: {storage_mb}MB")
total_storage_mb += storage_mb
if not is_stack_app:
storage_mb = self._aggregate_fixed_size_volumes_storage_mb(plugin_instance)
if storage_mb > 0:
self.Pd(f" FIXED_SIZE_VOLUMES storage: {storage_mb}MB")
total_storage_mb += storage_mb
fixed_storage_mb = self._aggregate_fixed_size_volumes_storage_mb(plugin_instance)
if fixed_storage_mb > 0:
self.Pd(f" FIXED_SIZE_VOLUMES storage: {fixed_storage_mb}MB")
storage_mb += fixed_storage_mb
has_storage_resource = True

plugin_name = plugin_instance.get(DEEPLOY_KEYS.PLUGIN_NAME)
if instance_id:
resource_key = (signature, DEEPLOY_KEYS.PLUGIN_INSTANCE_ID, str(instance_id))
elif plugin_name:
resource_key = (signature, DEEPLOY_KEYS.PLUGIN_NAME, str(plugin_name))
else:
resource_key = (signature, idx)

footprint = resource_footprints_by_key.get(resource_key)
if footprint is None:
resource_footprints_by_key[resource_key] = (cpu, memory_mb, storage_mb, has_storage_resource)
else:
prev_cpu, prev_memory_mb, prev_storage_mb, prev_has_storage_resource = footprint
resource_footprints_by_key[resource_key] = (
max(prev_cpu, cpu),
max(prev_memory_mb, memory_mb),
max(prev_storage_mb, storage_mb),
prev_has_storage_resource or has_storage_resource,
)
else:
self.Pd(f" Skipping non-container plugin: {signature}")

total_cpu = Decimal("0")
total_memory_mb = 0
total_storage_mb = 0
has_storage_resource = False
for cpu, memory_mb, storage_mb, footprint_has_storage_resource in resource_footprints_by_key.values():
total_cpu += cpu
total_memory_mb += memory_mb
total_storage_mb += storage_mb
has_storage_resource = has_storage_resource or footprint_has_storage_resource

# Return aggregated resources in standard format
aggregated = {
DEEPLOY_RESOURCES.CPU: self._format_cpu_decimal(total_cpu),
Expand Down
69 changes: 69 additions & 0 deletions extensions/business/deeploy/tests/test_stack_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,52 @@ def test_aggregate_container_resources_counts_selected_volume_storage_once(self)
self.assertEqual(resources[DEEPLOY_RESOURCES.MEMORY], "1536m")
self.assertEqual(resources[DEEPLOY_RESOURCES.STORAGE], "12288m")

def test_aggregate_container_resources_counts_replicated_plugin_name_once_per_node(self):
plugin = make_deeploy_plugin()
inputs = make_inputs(
job_app_type=JOB_APP_TYPES.STACK,
plugins=[
make_plugin_entry(
"CONTAINER_APP_RUNNER",
plugin_name="container-1",
CONTAINER_RESOURCES={"cpu": 0.5, "memory": "512m", "storage": "4g"},
),
make_plugin_entry(
"CONTAINER_APP_RUNNER",
plugin_name="container-1",
CONTAINER_RESOURCES={"cpu": 1, "memory": "1g", "storage": "8g"},
),
],
)

resources = plugin._aggregate_container_resources(inputs)

self.assertEqual(resources[DEEPLOY_RESOURCES.CPU], 1)
self.assertEqual(resources[DEEPLOY_RESOURCES.MEMORY], "1024m")
self.assertEqual(resources[DEEPLOY_RESOURCES.STORAGE], "8192m")

def test_aggregate_container_resources_keeps_unnamed_occurrences_additive(self):
plugin = make_deeploy_plugin()
inputs = make_inputs(
job_app_type=JOB_APP_TYPES.STACK,
plugins=[
make_plugin_entry(
"CONTAINER_APP_RUNNER",
CONTAINER_RESOURCES={"cpu": 0.5, "memory": "512m", "storage": "4g"},
),
make_plugin_entry(
"CONTAINER_APP_RUNNER",
CONTAINER_RESOURCES={"cpu": 0.5, "memory": "512m", "storage": "4g"},
),
],
)

resources = plugin._aggregate_container_resources(inputs)

self.assertEqual(resources[DEEPLOY_RESOURCES.CPU], 1)
self.assertEqual(resources[DEEPLOY_RESOURCES.MEMORY], "1024m")
self.assertEqual(resources[DEEPLOY_RESOURCES.STORAGE], "8192m")

def test_aggregate_container_resources_uses_two_decimal_cpu_boundaries(self):
plugin = make_deeploy_plugin()
inputs = make_inputs(
Expand Down Expand Up @@ -110,6 +156,29 @@ def test_stack_resources_may_fit_under_paid_tier(self):

self.assertTrue(plugin.deeploy_check_payment_and_job_owner(inputs, "0xowner", is_create=True))

def test_generic_duplicate_instance_id_is_rejected_before_paid_tier(self):
plugin = make_deeploy_plugin()
plugin.bc = _FakeBlockchain(job_type=1) # ENTRY: 1 CPU, 2GB RAM, 8GB storage
inputs = make_inputs(
job_id=123,
job_app_type=JOB_APP_TYPES.GENERIC,
plugins=[
make_plugin_entry(
"CONTAINER_APP_RUNNER",
instance_id="container-1",
CONTAINER_RESOURCES={"cpu": 1, "memory": "2g", "storage": "8g"},
),
make_plugin_entry(
"CONTAINER_APP_RUNNER",
instance_id="container-1",
CONTAINER_RESOURCES={"cpu": 1, "memory": "2g", "storage": "8g"},
),
],
)

with self.assertRaisesRegex(ValueError, DEEPLOY_ERRORS.PLUGINS3):
plugin.deeploy_check_payment_and_job_owner(inputs, "0xowner", is_create=True)

def test_stack_resources_reject_when_over_paid_tier(self):
plugin = make_deeploy_plugin()
plugin.bc = _FakeBlockchain(job_type=54) # LITE: 0.5 CPU, 1GB RAM, 4GB storage
Expand Down
2 changes: 1 addition & 1 deletion ver.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__VER__ = '2.10.370'
__VER__ = '2.10.380'
Loading