Skip to content
Merged
21 changes: 10 additions & 11 deletions coldfront/core/allocation/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,21 +208,20 @@ class AllocationApprovalForm(forms.Form):
required=False,
)

automation_specifications = forms.MultipleChoiceField(
label='If you have opted for automatic allocation creation, please select from the following options:',
required=False,
widget=forms.CheckboxSelectMultiple,
choices=(
('snapshots', 'Enable daily snapshots, 7 days of retention, for this allocation'),
('nfs_share', 'Create a NFS share for this allocation'),
('cifs_share', 'Create a CIFS share for this allocation'),
),
)
# automation_specifications = forms.MultipleChoiceField(
# label='If you have opted for automatic allocation creation, please select from the following options:',
# required=False,
# widget=forms.CheckboxSelectMultiple,
# choices=(
# ('nfs_share', 'Create a NFS share for this allocation'),
# ('cifs_share', 'Create a CIFS share for this allocation'),
# ),
# )

def clean(self):
cleaned_data = super().clean()
auto_create_opts = cleaned_data.get('auto_create_opts')
automation_specifications = cleaned_data.get('automation_specifications')
# automation_specifications = cleaned_data.get('automation_specifications')
# if the action is 'approve', make auto_create_opts and sheetcheck mandatory
if not auto_create_opts:
self.add_error(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,6 @@ <h5 class="modal-title" id="exampleModalLabel">Add New Account Name</h5>
message = `<div class="alert alert-warning" role="alert">Warning: you already have an allocation in the selected tier. Did you mean to submit an <a href=${url}>Allocation Change Request<\a> instead?</div>`;
messages.push(message);
}
if (resource == "1932") {
message = `<div class="alert alert-warning" role="alert">Lab Storage is currently being tested and implemented. It will be available for use starting February 2026.</div>`;
messages.push(message);
}
if (messages.length > 0) {
$('#resource_message').html(messages.join(''));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from django.core.management.base import BaseCommand
from django.db.utils import IntegrityError
from coldfront.core.resource.models import (
Resource,
ResourceType,
Expand Down Expand Up @@ -35,6 +34,8 @@ def handle(self, *args, **options):
('GPU Count', 'Int'),
('Features', 'Text'),
('slurm_integration', 'Text'),
('storage_type', 'Text'),
('url', 'Text'),
# UBCCR
('Core Count', 'Int'),
# ('expiry_time', 'Int'),
Expand Down Expand Up @@ -98,8 +99,7 @@ def handle(self, *args, **options):
('holylfs05/tier0', 'Holyoke data center lustre storage', True, storage, 'Tier 0', 1, True, True),
('holylfs06/tier0', 'Holyoke data center lustre storage', True, storage, 'Tier 0', 1, True, True),
('nesetape/tier3', 'Cold storage for past projects', True, storage, 'Tape', 20, True, True),
('holy-isilon/tier1', 'Tier1 storage with snapshots and disaster recovery copy', True, storage, 'Tier 1', 1, True, True),
('bos-isilon/tier1', 'Tier1 storage for on-campus storage mounting', True, storage, 'Tier 1', 1, True, True),
('lab-storage-nfs', 'NFS Lab Storage', True, storage, 'Lab Storage', 1, True, True),
('holystore01/tier0', 'Luster storage under Tier0', True, storage, 'Tier 0', 1, True, True),
('b-nfs02-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1, True, True),
('b-nfs03-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1, True, True),
Expand Down
31 changes: 16 additions & 15 deletions coldfront/plugins/fasrc/management/commands/pull_resource_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,20 @@ def handle(self, *args, **options):
# collect user and lab counts, allocation sizes for each volume
resources = Resource.objects.filter(resource_type__name='Storage')
# update all tier 0 resources
for resource in resources:
for resource in resources.filter(
resourceattribute__value__in=('isilon', 'powerscale')):
update_volume_information.send(sender=self.__class__, resource=resource)
for resource in resources.exclude(
resourceattribute__value__in=('isilon', 'powerscale')):
resource_name = resource.name.split('/')[0]
if 'isilon' in resource.name:
update_volume_information.send(sender=self.__class__, resource=resource)
else:
try:
volume = [v for v in volumes if v['name'] == resource_name][0]
except IndexError:
logger.warning('no data found for resource %s from source %s', resource.name, source)
continue
except Exception as e:
logger.warning('problem updating resource %s with data from source %s: %s',
resource.name, source, e
)
continue
update_resource_attr_types_from_dict(resource, volume['attrs'])
try:
volume = [v for v in volumes if v['name'] == resource_name][0]
except IndexError:
logger.warning('no data found for resource %s from source %s', resource.name, source)
continue
except Exception as e:
logger.warning('problem updating resource %s with data from source %s: %s',
resource.name, source, e
)
continue
update_resource_attr_types_from_dict(resource, volume['attrs'])
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from coldfront.core.resource.models import Resource
from coldfront.plugins.isilon.utils import (
IsilonConnection,
get_isilon_url,
print_log_error,
update_coldfront_quota_and_usage,
)
Expand All @@ -18,18 +19,17 @@ class Command(BaseCommand):
help = 'Pull Isilon quotas'

def handle(self, *args, **kwargs):
"""For all active tier1 allocations, update quota and usage
1. run a query that collects all active tier1 allocations
"""For all active isilon and powerscale allocations, update quota and usage
"""
quota_bytes_attributetype = AllocationAttributeType.objects.get(
name='Quota_In_Bytes')
quota_tbs_attributetype = AllocationAttributeType.objects.get(
name='Storage Quota (TiB)')
# create isilon connections to all isilon clusters in coldfront
isilon_resources = Resource.objects.filter(name__contains='tier1')
isilon_resources = Resource.objects.filter(resourceattribute__value__in=('isilon', 'powerscale'))
for resource in isilon_resources:
report = {"complete": 0, "no entry": [], "empty quota": []}
resource_name = resource.name.split('/')[0]
resource_name = get_isilon_url(resource)
# try connecting to the cluster. If it fails, display an error and
# replace the resource with a dummy resource
try:
Expand Down
19 changes: 10 additions & 9 deletions coldfront/plugins/isilon/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
allocation_autocreate,
allocation_autoupdate,
)
from coldfront.core.resource.models import ResourceAttributeType
from coldfront.core.resource.models import Resource, ResourceAttributeType
from coldfront.core.resource.signals import update_volume_information
from coldfront.plugins.isilon.utils import (
IsilonConnection,
create_isilon_allocation_quota,
get_isilon_url,
update_isilon_allocation_quota,
)

Expand All @@ -19,8 +20,8 @@
def update_isilon_volume_information(sender, **kwargs):

resource = kwargs['resource']
if 'isilon' in resource.name:
resource_name = resource.name.split('/')[0]
if resource in Resource.objects.filter(resourceattribute__value__in=('isilon', 'powerscale')):
resource_name = get_isilon_url(resource)
isilon_api = IsilonConnection(resource_name)
isilon_capacity_tb = isilon_api.to_tb(isilon_api.total_space)
isilon_free_tb = isilon_api.to_tb(isilon_api.unused_space)
Expand All @@ -43,17 +44,17 @@ def update_isilon_volume_information(sender, **kwargs):
@receiver(allocation_autocreate)
def activate_allocation(sender, **kwargs):

approval_form_data = kwargs['approval_form_data']
# approval_form_data = kwargs['approval_form_data']
allocation_obj = kwargs['allocation_obj']
resource = kwargs['resource']

automation_specifications = approval_form_data.get('automation_specifications')
automation_kwargs = {k:True for k in automation_specifications}
# automation_specifications = approval_form_data.get('automation_specifications')
# automation_kwargs = {k:True for k in automation_specifications}

if 'isilon' in resource.name:
if resource in Resource.objects.filter(resourceattribute__value__in=('isilon', 'powerscale')):
try:
option_exceptions = create_isilon_allocation_quota(
allocation_obj, resource, **automation_kwargs
allocation_obj, resource # , **automation_kwargs
)
if option_exceptions:
err = f'some options failed to be created for new allocation {allocation_obj} ({allocation_obj.pk}): {option_exceptions}'
Expand All @@ -69,7 +70,7 @@ def update_allocation_quota(sender, **kwargs):
allocation_obj = kwargs['allocation_obj']
new_quota_value = kwargs['new_quota_value']
resource = allocation_obj.resources.first()
if 'isilon' in resource.name:
if resource in Resource.objects.filter(resourceattribute__value__in=('isilon', 'powerscale')):
try:
update_isilon_allocation_quota(allocation_obj, new_quota_value)
logger.info(
Expand Down
4 changes: 2 additions & 2 deletions coldfront/plugins/isilon/tasks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from coldfront.plugins.isilon.utils import update_quotas_usages
from django.core import management

def pull_isilon_quotas():
"""Pull Isilon quotas
"""
update_quotas_usages()
management.call_command('pull_isilon_quotas')
12 changes: 7 additions & 5 deletions coldfront/plugins/isilon/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
from coldfront.core.allocation.models import Allocation, AllocationStatusChoice
from coldfront.core.project.models import Project
from coldfront.core.resource.models import Resource
from coldfront.plugins.isilon.utils import IsilonConnection, create_isilon_allocation_quota
from coldfront.plugins.isilon.utils import (
IsilonConnection,
create_isilon_allocation_quota,
get_isilon_url,
)


def test_create_isilon_allocation_quota():
Expand All @@ -18,9 +22,9 @@ def test_create_isilon_allocation_quota():

path = f'ifs/rc_labs/{allocation.project.title}'
# for each isilon cluster:
for resource in Resource.objects.filter(name__contains='tier1'):
for resource in Resource.objects.filter(resourceattribute__value__in=('isilon', 'powerscale')):
# create isilon IsilonConnection
isilon_connection = IsilonConnection(resource.name)
isilon_connection = IsilonConnection(get_isilon_url(resource))
# run create_isilon_allocation_quota on the allocation with the isilon cluster
create_isilon_allocation_quota(allocation, resource)

Expand All @@ -31,8 +35,6 @@ def test_create_isilon_allocation_quota():
)
# confirm that acl mode is 2770
assert acl.mode == '2770'
# confirm that
print(acl)
# check that the directory quota is properly created
quota_list = isilon_connection.quota_client.list_quota_quotas()
quota = next(q for q in quota_list.quotas if q.path == f'/{path}')
Expand Down
51 changes: 32 additions & 19 deletions coldfront/plugins/isilon/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@
except:
logger.warning("no ldap plugin; isilon auth model will have issues")

def get_isilon_url(resource):
"""Return the Isilon API URL from the `url` resource attribute."""
if isinstance(resource, str):
return resource

value = resource.get_attribute('url', expand=False, typed=False)
if value:
return str(value).strip()

raise ValueError(
f"Missing required ResourceAttributeType 'url' for resource {resource.name}"
)

class IsilonConnection:
"""Convenience class containing methods for collecting data from an isilon cluster
"""
Expand All @@ -33,9 +46,9 @@ def __init__(self, cluster_name):
self._total_space = None
self._used_space = None

def connect(self, cluster_name):
def connect(self, url):
configuration = isilon_api.Configuration()
configuration.host = f'http://{cluster_name}01.rc.fas.harvard.edu:8080'
configuration.host = url
configuration.username = import_from_settings('ISILON_USER')
configuration.password = import_from_settings('ISILON_PASS')
configuration.verify_ssl = False
Expand Down Expand Up @@ -183,12 +196,12 @@ def return_isilon_group_id(self):


def create_isilon_allocation_quota(
allocation, resource, snapshots=False, nfs_share=False, cifs_share=False
allocation, resource, nfs_share=True, cifs_share=True
):
"""Create a new isilon allocation quota
"""
lab_name = allocation.project.title
isilon_resource = resource.name.split('/')[0]
isilon_resource = get_isilon_url(resource)
isilon_conn = IsilonConnection(isilon_resource)
actions_performed = []
# determine whether rc_labs or rc_fasse_labs path
Expand Down Expand Up @@ -263,20 +276,19 @@ def create_isilon_allocation_quota(
actions_performed.append('quota set')

option_exceptions = {}
if snapshots:
### set up snapshots for the created quota ###
snapshot_schedule = isilon_api.SnapshotScheduleCreateParams(
name=lab_name,
path=f'/{path}',
pattern=f"{lab_name}_daily_%Y-%m-%d-_%H-%M",
schedule="Every 1 days",
duration=7*24*60*60,
)
try:
isilon_conn.snapshot_client.create_snapshot_schedule(snapshot_schedule)
actions_performed.append('snapshots scheduled')
except Exception as e:
option_exceptions['snapshots'] = e
### set up snapshots for the created quota ###
snapshot_schedule = isilon_api.SnapshotScheduleCreateParams(
name=lab_name,
path=f'/{path}',
pattern=f"{lab_name}_daily_%Y-%m-%d-_%H-%M",
schedule="Every 1 days",
duration=7*24*60*60,
)
try:
isilon_conn.snapshot_client.create_snapshot_schedule(snapshot_schedule)
actions_performed.append('snapshots scheduled')
except Exception as e:
option_exceptions['snapshots'] = e

if nfs_share:
### set up NFS export ###
Expand Down Expand Up @@ -344,7 +356,8 @@ def update_isilon_allocation_quota(allocation, new_quota):
quota : int
"""
# make isilon connection to the allocation's resource
isilon_resource = allocation.resources.first().name.split('/')[0]
resource = allocation.resources.first()
isilon_resource = get_isilon_url(resource)
isilon_conn = IsilonConnection(isilon_resource)
path = f'/ifs/{allocation.path}'

Expand Down
Loading