Skip to content

Commit 6a87c5a

Browse files
committed
shared drives: sync deletion state, set deletion date when expiring, unset when rescuing from deletion
1 parent 8543186 commit 6a87c5a

File tree

13 files changed

+243
-21
lines changed

13 files changed

+243
-21
lines changed

docker/settings.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,14 @@
8080
else:
8181
RESTCLIENTS_PRT_HOST = 'https://depts.washington.edu'
8282
RESTCLIENTS_PRT_DAO_CLASS = 'Live'
83-
RESTCLIENTS_MSCA_HOST = os.getenv("MSCA_HOST", '')
8483
RESTCLIENTS_MSCA_DAO_CLASS = 'Live'
8584
RESTCLIENTS_MSCA_TIMEOUT = os.getenv(
8685
"MSCA_TIMEOUT", RESTCLIENTS_DEFAULT_TIMEOUT)
8786
RESTCLIENTS_MSCA_SUBSCRIPTION_KEY = os.getenv('MSCA_SUBSCRIPTION_KEY', '')
87+
if os.getenv('MSCA_ENV') == 'PROD':
88+
RESTCLIENTS_MSCA_HOST = 'https://pplat-main-apim.azure-api.net'
89+
else:
90+
RESTCLIENTS_MSCA_HOST = 'https://pplat-apimgmt.azure-api.net'
8891

8992
RESTCLIENTS_ITBILL_DAO_CLASS = 'Live'
9093
RESTCLIENTS_ITBILL_HOST=os.getenv('ITBILL_HOST')

endorsement/dao/shared_drive.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@
66
import re
77
import logging
88

9+
from django.utils import timezone
10+
911
# methods are imported to populate namespace and be used elsewhere
1012
from uw_msca.shared_drive import ( # noqa: F401
1113
get_default_quota,
1214
get_google_drive_states,
1315
set_drive_quota,
16+
mark_drive_for_deletion,
17+
rescue_drive_from_deletion,
1418
)
1519
from uw_msca.models import GoogleDriveState
1620

@@ -52,6 +56,7 @@
5256
)
5357
MISSING_DRIVE_THRESHOLD = 500
5458
MISSING_DRIVE_NOTIFICATION = 150
59+
PENDING_DELETE_ORG_UNIT = "deleteprt"
5560

5661

5762
def sync_quota_from_subscription(drive_id):
@@ -87,7 +92,7 @@ def expire_shared_drives(gracetime, lifetime):
8792
shared_drive_lifecycle_expired(drive)
8893

8994

90-
def shared_drive_lifecycle_expired(drive_record):
95+
def shared_drive_lifecycle_expired(shared_drive):
9196
"""
9297
Set lifecycle to expired for shared drive
9398
@@ -96,8 +101,20 @@ def shared_drive_lifecycle_expired(drive_record):
96101
- set subscription end_date to today using:
97102
- expire_subscription(drive_record)
98103
"""
99-
logger.error(
100-
f"Shared drive {drive_record} lifecycle expired: not implemented")
104+
logger.info(f"Shared drive {shared_drive.drive_id} marked for deletion")
105+
mark_drive_for_deletion(shared_drive.drive_id)
106+
107+
108+
def rescue_shared_drive_from_deletion(shared_drive):
109+
"""
110+
Restore OrgUnit for shared drive previously marked for deletion
111+
112+
Actions:
113+
- set call msca rescue method with original drive quota
114+
"""
115+
logger.info(f"Rescue shared drive {shared_drive} from deletion")
116+
rescue_drive_from_deletion(
117+
shared_drive.drive_quota.quota_limit, shared_drive.drive_id)
101118

102119

103120
def load_shared_drives(google_drive_states):
@@ -608,9 +625,21 @@ def managers_for_shared_drive(google_drive_states):
608625
f"existing drive ({drive_id}) "
609626
f"usage ({drive_state.drive_name}) update: {ex}")
610627

611-
# confirm drive and subscription match
612628
sdr = SharedDriveRecord.objects.get_record_by_drive_id(
613629
drive_id)
630+
631+
# drive reported in pending delete org unit, store deleted date if
632+
# not present to help signal state in the ui
633+
if (shared_drive.drive_quota.org_unit_name != drive_state.org_unit_name
634+
and drive_state.org_unit_name.lower() == PENDING_DELETE_ORG_UNIT
635+
and sdr.datetime_deleted is None):
636+
logger.info(f"drive quota org unit change: drive {drive_id} "
637+
f"org unit {shared_drive.drive_quota.org_unit_name} "
638+
f"to {drive_state.org_unit_name}")
639+
sdr.datetime_deleted = timezone.now()
640+
sdr.save()
641+
642+
# confirm drive and subscription match
614643
reconcile_drive_quota(
615644
sdr,
616645
no_subscription_quota=subsidized_quota,

endorsement/fixtures/test_data/shared_drive.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,16 @@
7575
"drive_quota": 1,
7676
"members": [1, 4, 5, 7, 10, 11]
7777
}
78+
},
79+
{
80+
"model": "endorsement.shareddrive",
81+
"pk": 8,
82+
"fields": {
83+
"drive_id": "ABC_0123-DF56AA1234",
84+
"drive_name": "PRT_DELETE_ON_XX_Subsidized quota",
85+
"drive_usage": 63,
86+
"drive_quota": 1,
87+
"members": [1, 4, 5]
88+
}
7889
}
7990
]

endorsement/fixtures/test_data/shared_drive_record.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,5 +124,23 @@
124124
"datetime_deleted": null,
125125
"is_deleted": null
126126
}
127+
},
128+
{
129+
"model": "endorsement.shareddriverecord",
130+
"pk": 8,
131+
"fields": {
132+
"shared_drive": 8,
133+
"subscription": null,
134+
"datetime_created": "2024-02-12T17:41:28+00:00",
135+
"datetime_emailed": null,
136+
"datetime_notice_1_emailed": null,
137+
"datetime_notice_2_emailed": null,
138+
"datetime_notice_3_emailed": null,
139+
"datetime_notice_4_emailed": null,
140+
"datetime_accepted": "2024-02-12T17:41:28+00:00",
141+
"datetime_expired": "2026-02-12T17:41:28+00:00",
142+
"datetime_deleted": "2026-02-12T17:41:28+00:00",
143+
"is_deleted": null
144+
}
127145
}
128146
]
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Copyright 2026 UW-IT, University of Washington
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
from django.core.management.base import BaseCommand
5+
from django.utils import timezone
6+
from restclients_core.exceptions import DataFailureException
7+
from endorsement.policy.shared_drive import SharedDrivePolicy
8+
from endorsement.dao.shared_drive import shared_drive_lifecycle_expired
9+
import csv
10+
import logging
11+
12+
13+
logger = logging.getLogger(__name__)
14+
15+
16+
class Command(BaseCommand):
17+
help = 'mark shared drives past their renewal date for deletion'
18+
19+
def add_arguments(self, parser):
20+
parser.add_argument(
21+
'-c',
22+
'--commit',
23+
action='store_true',
24+
dest='actually_mark_for_deletion',
25+
default=False,
26+
help='Commit marking shared drive for deletion (default: log only)',
27+
)
28+
parser.add_argument(
29+
'-d',
30+
'--dump_drive_csv',
31+
action='store_true',
32+
dest='dump_drive_csv',
33+
default=False,
34+
help='Dump CSV of shared drives that would be marked for deletion',
35+
)
36+
37+
def handle(self, *args, **options):
38+
actually_mark_for_deletion = options['actually_mark_for_deletion']
39+
dump_drive_csv = options['dump_drive_csv']
40+
41+
if dump_drive_csv:
42+
self.drive_writer = csv.writer(self.stdout)
43+
self.drive_writer.writerow(["Drive ID", "Drive Name", "Manager NetIDs"])
44+
45+
for record in SharedDrivePolicy().records_to_expire():
46+
if dump_drive_csv:
47+
self._record_csv(record)
48+
continue
49+
50+
logger.info(f'Expiring: {record.shared_drive.drive_id} '
51+
f'"{record.shared_drive.drive_name}"')
52+
53+
if actually_mark_for_deletion:
54+
self.expire_drive(record)
55+
56+
def expire_drive(self, record):
57+
try:
58+
shared_drive_lifecycle_expired(record.shared_drive)
59+
record.datetime_deleted = timezone.now()
60+
record.save()
61+
except DataFailureException as ex:
62+
logger.error(
63+
"Cannot expire drive {record.shared_drive.drive_id} : {ex}")
64+
65+
def _record_csv(self, record):
66+
self.drive_writer.writerow([
67+
record.shared_drive.drive_id,
68+
record.shared_drive.drive_name,
69+
",".join([n.member.netid for (
70+
n) in record.shared_drive.get_members()])])

endorsement/models/shared_drive.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,16 +267,16 @@ def set_acceptance(self, member_netid, accept=True, acted_as=None):
267267
if accept:
268268
self.datetime_accepted = acceptance.datetime_created
269269
self.datetime_expired = None
270+
self.datetime_deleted = None
270271
else:
271272
self.datetime_expired = acceptance.datetime_accepted
272273

273-
# clear notification schedule
274+
# clear lifecycle state
274275
self.datetime_emailed = None
275276
self.datetime_notice_1_emailed = None
276277
self.datetime_notice_2_emailed = None
277278
self.datetime_notice_3_emailed = None
278279
self.datetime_notice_4_emailed = None
279-
280280
self.save()
281281

282282
def update_subscription(self, itbill):

endorsement/policy/shared_drive.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,6 @@ def record_model(self):
2121
@property
2222
def datetime_provisioned_key(self):
2323
return "datetime_accepted"
24+
25+
def additional_warning_terms(self):
26+
return {"datetime_deleted__isnull": True}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"message": "Drive 'ABC_0123-DE45FF5789' successfully deleted"}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"message": "Drive 'ABC_0123-DF56AA1234' successfully moved 100GB OU"}

endorsement/static/endorsement/js/tab/google.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ var ManageSharedDrives = (function () {
8383
_updateSharedDrivesDisplay(drive);
8484
Notify.success('Shared drive "' + drive.drive.drive_name + '" provision renewed.', 10000);
8585
}).on('endorse:SharedDriveResponsibilityAcceptedError', function (e, error) {
86-
Notify.error('Sorry, but we cannot accept responsibility at this time: ' + error);
86+
_modalHide();
87+
Notify.error('Sorry, but we cannot Renew shared drive at this time: ' + error);
8788
}).on('change', '#shared_drive_modal input', function () {
8889
var $modal = $(this).closest('#shared_drive_modal'),
8990
$accept_button = $('button.accept-button', $modal),
@@ -188,6 +189,7 @@ var ManageSharedDrives = (function () {
188189
},
189190
_prepSharedDriveContext = function (drive) {
190191
var expiration = moment(drive.datetime_expiration),
192+
deletion = drive.datetime_deleted ? moment(drive.datetime_deleted) : null,
191193
deadline = moment(drive.datetime_subscription_deadline),
192194
now = moment.utc(),
193195
billing_period_start = moment(((now.month() == 11) ? now.year() : now.year() - 1) + '-12-01'),
@@ -197,6 +199,8 @@ var ManageSharedDrives = (function () {
197199
drive.expiration_days = expiration.diff(now, 'days');
198200
drive.expiration_from_now = expiration.from(now);
199201

202+
drive.deletion_date = deletion ? deletion.format('M/D/YYYY') : null;
203+
200204
drive.subscription_deadline_date = deadline.format('M/D/YYYY');
201205
drive.subscription_deadline_from_now = deadline.from(now);
202206

0 commit comments

Comments
 (0)