Skip to content

Commit b8bfbd6

Browse files
committed
add drafts of utils and first signal
1 parent 96ae21e commit b8bfbd6

3 files changed

Lines changed: 144 additions & 0 deletions

File tree

coldfront/plugins/ecs/apps.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from django.apps import AppConfig
2+
3+
4+
class SlurmConfig(AppConfig):
5+
name = 'coldfront.plugins.ecs'
6+
7+
8+
def ready(self):
9+
import coldfront.plugins.ecs.signals

coldfront/plugins/ecs/signals.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import logging
2+
3+
from django.dispatch import receiver
4+
from coldfront.core.allocation.signals import allocation_autocreate
5+
from coldfront.plugins.ecs.utils import ECSResourceManager
6+
7+
logger = logging.getLogger(__name__)
8+
9+
@receiver(allocation_autocreate)
10+
def activate_allocation(sender, **kwargs):
11+
approval_form_data = kwargs['approval_form_data']
12+
allocation_obj = kwargs['allocation_obj']
13+
resource = kwargs['resource']
14+
15+
automation_specifications = approval_form_data.get('automation_specifications')
16+
automation_kwargs = {k:True for k in automation_specifications}
17+
18+
if 'ecs' in resource.name:
19+
try:
20+
ecs_manager = ECSResourceManager(resource)
21+
block_limit_tb = allocation_obj.size_tb
22+
ecs_manager.create_allocation_bucket(allocation_obj.lab.name, block_limit_tb)
23+
except Exception as e:
24+
err = ("An error was encountered while auto-creating the "
25+
"allocation. Please contact Coldfront administration "
26+
f"and/or manually create the allocation: {e}")
27+
logger.error(err)
28+
raise ValueError(err)
29+
return 'ecs'

coldfront/plugins/ecs/utils.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import logging
2+
3+
from ecsclient.client import Client
4+
5+
from coldfront.core.utils.common import import_from_settings
6+
7+
ECS_CLIENT_VERSION = import_from_settings('ECS_CLIENT_VERSION', '3')
8+
ECS_USER = import_from_settings('ECS_USER')
9+
ECS_PASS = import_from_settings('ECS_PASS')
10+
11+
logger = logging.getLogger(__name__)
12+
13+
14+
class ECSResourceManager():
15+
"""Class for managing objects related to an ECS cluster."""
16+
17+
def __init__(self, resource, username=ECS_USER, password=ECS_PASS):
18+
self.resource = resource
19+
self.url = resource.resourceattribute_set.get(resource_attribute_type__name='url').value
20+
self._username = username
21+
self._password = password
22+
self.client = self.connect()
23+
24+
25+
def connect(self):
26+
client = Client(
27+
ECS_CLIENT_VERSION,
28+
username=self._username,
29+
password=self._password,
30+
token_endpoint=f'{self.url}:4443/login',
31+
ecs_endpoint=f'{self.url}:4443'
32+
)
33+
return client
34+
35+
def generate_token(self, username, password):
36+
"""Generate a token for ECS API access."""
37+
38+
def create_allocation_bucket(self, lab_name, block_limit_tb):
39+
"""Create a quota for a tenant."""
40+
bucket_name = f"lab-{lab_name}-bucket"
41+
block_limit_gb = block_limit_tb * 1024
42+
notification_limit_gb = int(block_limit_gb * 0.9)
43+
try:
44+
self.client.bucket.create(bucket_name, namespace=lab_name,
45+
replication_group='', filesystem_enabled=False,
46+
head_type=None, stale_allowed=None,
47+
metadata=None, encryption_enabled=False
48+
)
49+
except Exception as e:
50+
logger.exception("Error creating bucket %s: %s", bucket_name, str(e))
51+
raise
52+
self.client.bucket.set_quota(
53+
bucket_name,
54+
block_size=block_limit_gb,
55+
notification_size=notification_limit_gb,
56+
)
57+
58+
def change_bucket_quota(self, bucket_name, namespace_name, new_block_size_tb):
59+
"""Change a quota for a tenant."""
60+
# possibly use this in create_allocation_bucket as well
61+
new_block_size_gb = new_block_size_tb * 1024
62+
new_notification_size_gb = int(new_block_size_gb * 0.9)
63+
64+
self.client.bucket.set_quota(
65+
bucket_name,
66+
namespace=namespace_name,
67+
block_size=new_block_size_gb,
68+
notification_size=new_notification_size_gb
69+
)
70+
71+
def delete_allocation_bucket(self, bucket_name, namespace_name):
72+
"""Delete a quota for a tenant."""
73+
self.client.bucket.delete(bucket_name, namespace=namespace_name)
74+
75+
def update_resource_usage_data(self):
76+
"""Get system usage data and update the corresponding resource records."""
77+
capacity_dict = self.client.capacity.get_cluster_capacity()
78+
allocated_tb = capacity_dict['totalProvisioned_gb'] / 1024
79+
free_tb = capacity_dict['totalFree_gb'] / 1024
80+
capacity_tb = allocated_tb + free_tb
81+
tb_dict = {'allocated_tb': allocated_tb, 'free_tb': free_tb, 'capacity_tb': capacity_tb}
82+
for k, v in tb_dict.items():
83+
logger.info("ECS Capacity %s: %.2f TB", k, v)
84+
attribute = self.resource.resourceattribute_set.get(resource_attribute_type__name=k)
85+
attribute.value = v
86+
attribute.save()
87+
return capacity_dict
88+
89+
def update_bucket_allocation_usage_data(self, allocation, bucket_name, namespace_name):
90+
"""Get bucket usage data and update the corresponding allocation records."""
91+
# for getting bucket stats:
92+
bucket_stats = self.client.billing.get_bucket_billing_info(
93+
bucket_name, namespace_name, sizeunit='KB')
94+
total_size_tb = bucket_stats['total_size'] / (1024 * 1024 * 1024)
95+
total_size_bytes = bucket_stats['total_size'] * 1024
96+
# update usage in bytes
97+
quota_bytes_attr = allocation.allocationattribute_set.get(
98+
allocation_attribute_type__name='Quota_In_Bytes')
99+
quota_bytes_attr.usage = total_size_bytes
100+
quota_bytes_attr.save()
101+
# update usage in TB
102+
quota_tb_attr = allocation.allocationattribute_set.get(
103+
allocation_attribute_type__name='Storage Quota (TB)'
104+
)
105+
quota_tb_attr.usage = total_size_tb
106+
quota_tb_attr.save()

0 commit comments

Comments
 (0)