@ -2,15 +2,16 @@ from collections import defaultdict
import datetime
import json
import os
from typing import Dict
from typing import Dict , List
from zope import component
from zope . interface import implementer
from ceo_common . errors import UserNotFoundError
from ceo_common . logger_factory import logger_factory
from ceo_common . interfaces import ICloudResourceManager , \
from ceo_common . interfaces import ICloudResourceManager , IUser , \
ILDAPService , IMailService , IKubernetesService , IVHostManager , \
ICloudStackService
ICloudStackService , IContainerRegistryService
from ceo_common . model import Term
import ceo_common . utils as utils
@ -26,79 +27,133 @@ class CloudResourceManager:
self . pending_deletions_file = \
os . path . join ( state_dir , ' pending_account_deletions.json ' )
def purge_accounts ( self ) - > Dict :
accounts_deleted = [ ]
accounts_to_be_deleted = [ ]
result = {
' accounts_deleted ' : accounts_deleted ,
' accounts_to_be_deleted ' : accounts_to_be_deleted ,
}
current_term = Term . current ( )
beginning_of_term = current_term . to_datetime ( )
now = utils . get_current_datetime ( )
delta = now - beginning_of_term
if delta . days < 30 :
# one-month grace period
return result
@staticmethod
def _should_not_have_resources_deleted ( user : IUser ) - > bool :
return not user . is_member ( ) or user . membership_is_valid ( )
ldap_srv = component . getUtility ( ILDAPService )
mail_srv = component . getUtility ( IMailService )
def _get_resources_for_each_user ( self ) - > Dict [ str , Dict ] :
"""
Get a list of cloud resources each user is using .
The returned dict looks like
{
" ctdalek " : {
" resources " : [ " cloudstack " , " k8s " , . . . ] ,
" cloudstack_account_id " : " 3452345-2453245-23453... "
} ,
. . .
}
The " cloudstack_account_id " key will only be present if the user
has a CloudStack account .
"""
k8s_srv = component . getUtility ( IKubernetesService )
vhost_mgr = component . getUtility ( IVHostManager )
cloudstack_srv = component . getUtility ( ICloudStackService )
reg_srv = component . getUtility ( IContainerRegistryService )
accounts = defaultdict ( lambda : { ' resources ' : [ ] } )
# get a list of all cloud services each user is using
accounts = defaultdict ( list )
cloudstack_accounts = cloudstack_srv . get_accounts ( )
# note that cloudstack_accounts is a dict, not a list
for username in cloudstack_accounts :
accounts [ username ] . append ( ' cloudstack ' )
for username , account_id in cloudstack_accounts . items ( ) :
accounts [ username ] [ ' resources ' ] . append ( ' cloudstack ' )
accounts [ username ] [ ' cloudstack_account_id ' ] = account_id
vhost_accounts = vhost_mgr . get_accounts ( )
for username in vhost_accounts :
accounts [ username ] . append ( ' vhost ' )
accounts [ username ] [ ' resources ' ] . append ( ' vhost ' )
k8s_accounts = k8s_srv . get_accounts ( )
for username in k8s_accounts :
accounts [ username ] . append ( ' k8s ' )
if os . path . isfile ( self . pending_deletions_file ) :
state = json . load ( open ( self . pending_deletions_file ) )
last_check = datetime . datetime . fromtimestamp ( state [ ' timestamp ' ] )
delta = now - last_check
if delta . days < 7 :
logger . debug (
' Skipping account purge because less than one week has '
' passed since the warning emails were sent out '
)
accounts_to_be_deleted . extend ( state [ ' accounts_to_be_deleted ' ] )
return result
for username in state [ ' accounts_to_be_deleted ' ] :
if username not in accounts :
continue
accounts [ username ] [ ' resources ' ] . append ( ' k8s ' )
reg_accounts = reg_srv . get_accounts ( )
for username in reg_accounts :
accounts [ username ] [ ' resources ' ] . append ( ' registry ' )
return accounts
def _perform_deletions (
self ,
state : Dict ,
accounts : Dict [ str , Dict ] ,
accounts_deleted : List [ str ] ,
) :
ldap_srv = component . getUtility ( ILDAPService )
mail_srv = component . getUtility ( IMailService )
k8s_srv = component . getUtility ( IKubernetesService )
vhost_mgr = component . getUtility ( IVHostManager )
cloudstack_srv = component . getUtility ( ICloudStackService )
reg_srv = component . getUtility ( IContainerRegistryService )
for username in state [ ' accounts_to_be_deleted ' ] :
if username not in accounts :
continue
try :
user = ldap_srv . get_user ( username )
if user . membership_is_valid ( ) :
continue
services = accounts [ username ]
if ' cloudstack ' in services :
account_id = cloudstack_accounts [ username ]
cloudstack_srv . delete_account ( account_id )
if ' vhost ' in services :
vhost_mgr . delete_all_vhosts_for_user ( username )
if ' k8s ' in services :
k8s_srv . delete_account ( username )
accounts_deleted . append ( username )
mail_srv . send_cloud_account_has_been_deleted_message ( user )
logger . info ( f ' Deleted cloud resources for { username } ' )
os . unlink ( self . pending_deletions_file )
return result
except UserNotFoundError :
continue
if self . _should_not_have_resources_deleted ( user ) :
continue
resources = accounts [ username ] [ ' resources ' ]
if ' cloudstack ' in resources :
account_id = accounts [ username ] [ ' cloudstack_account_id ' ]
cloudstack_srv . delete_account ( account_id )
if ' vhost ' in resources :
vhost_mgr . delete_all_vhosts_for_user ( username )
if ' k8s ' in resources :
k8s_srv . delete_account ( username )
if ' registry ' in resources :
reg_srv . delete_project_for_user ( username )
accounts_deleted . append ( username )
mail_srv . send_cloud_account_has_been_deleted_message ( user )
logger . info ( f ' Deleted cloud resources for { username } ' )
def _perform_deletions_if_warning_period_passed (
self ,
now : datetime . datetime ,
accounts : Dict [ str , Dict ] ,
accounts_deleted : List [ str ] ,
accounts_to_be_deleted : List [ str ] ,
) :
state = json . load ( open ( self . pending_deletions_file ) )
last_check = datetime . datetime . fromtimestamp ( state [ ' timestamp ' ] )
delta = now - last_check
if delta . days < 7 :
logger . debug (
' Skipping account purge because less than one week has '
' passed since the warning emails were sent out '
)
accounts_to_be_deleted . extend ( state [ ' accounts_to_be_deleted ' ] )
return
self . _perform_deletions ( state , accounts , accounts_deleted )
os . unlink ( self . pending_deletions_file )
def _in_grace_period ( self , now : datetime . datetime ) - > bool :
current_term = Term . current ( )
beginning_of_term = current_term . to_datetime ( )
delta = now - beginning_of_term
# one-month grace period
return delta . days < 30
def _send_out_warning_emails (
self ,
now : datetime . datetime ,
accounts : Dict [ str , dict ] ,
accounts_to_be_deleted : List [ str ] ,
) :
ldap_srv = component . getUtility ( ILDAPService )
mail_srv = component . getUtility ( IMailService )
state = {
' timestamp ' : int ( now . timestamp ( ) ) ,
' accounts_to_be_deleted ' : accounts_to_be_deleted ,
}
for username in accounts :
user = ldap_srv . get_user ( username )
if user . membership_is_valid ( ) :
try :
user = ldap_srv . get_user ( username )
except UserNotFoundError :
logger . warning ( f ' User { username } not found ' )
continue
if self . _should_not_have_resources_deleted ( user ) :
continue
accounts_to_be_deleted . append ( username )
mail_srv . send_cloud_account_will_be_deleted_message ( user )
@ -108,4 +163,30 @@ class CloudResourceManager:
)
if accounts_to_be_deleted :
json . dump ( state , open ( self . pending_deletions_file , ' w ' ) )
def _warning_emails_were_sent_out ( self ) - > bool :
return os . path . isfile ( self . pending_deletions_file )
def purge_accounts ( self ) - > Dict :
accounts_deleted = [ ]
accounts_to_be_deleted = [ ]
result = {
' accounts_deleted ' : accounts_deleted ,
' accounts_to_be_deleted ' : accounts_to_be_deleted ,
}
now = utils . get_current_datetime ( )
if self . _in_grace_period ( now ) :
return result
# get a list of all cloud services each user is using
accounts = self . _get_resources_for_each_user ( )
if self . _warning_emails_were_sent_out ( ) :
self . _perform_deletions_if_warning_period_passed (
now , accounts , accounts_deleted , accounts_to_be_deleted )
return result
self . _send_out_warning_emails ( now , accounts , accounts_to_be_deleted )
return result