from collections import defaultdict import datetime import json import os from typing import Dict from zope import component from zope.interface import implementer from ceo_common.logger_factory import logger_factory from ceo_common.interfaces import ICloudResourceManager, \ ILDAPService, IMailService, IKubernetesService, IVHostManager, \ ICloudStackService from ceo_common.model import Term import ceo_common.utils as utils logger = logger_factory(__name__) @implementer(ICloudResourceManager) class CloudResourceManager: def __init__(self): state_dir = '/run/ceod' if not os.path.isdir(state_dir): os.mkdir(state_dir) 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 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) # 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') vhost_accounts = vhost_mgr.get_accounts() for username in vhost_accounts: accounts[username].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 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 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(): continue accounts_to_be_deleted.append(username) mail_srv.send_cloud_account_will_be_deleted_message(user) logger.info( f'A warning email was sent to {username} because their ' 'cloud account will be deleted' ) if accounts_to_be_deleted: json.dump(state, open(self.pending_deletions_file, 'w')) return result