text: """
  Copyright 2016 Platform9 Systems Inc.(http://www.platform9.com)
  
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at
  
      http://www.apache.org/licenses/LICENSE-2.0
  
  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
  """
  
  from datetime import datetime, timedelta
  
  from .leasehandler import get_lease_handler
  from .persistence import DbPersistence
  from eventlet.greenthread import spawn_after
  import logging
  from .leasehandler.constants import SUCCESS_OK, ERR_UNKNOWN, ERR_NOT_FOUND
  
  logger = logging.getLogger(__name__)
  
  
  def get_tenant_lease_data(data):
      """
      Simple function to transform tenant database proxy object into an externally
      consumable dictionary.
      :param data: database row object
      """
      return {'vm_lease_policy': {'tenant_uuid': data['tenant_uuid'],
                                  'expiry_mins': data['expiry_mins'],
                                  'created_at': data['created_at'],
                                  'created_by': data['created_by'],
                                  'updated_at': data['updated_at'],
                                  'updated_by': data['updated_by']}}
  
  
  def get_vm_lease_data(data):
      """
      Simple function to transform instance database proxy object into an externally
      consumable dictionary.
      :param data: database row object
      """
      return {'instance_uuid': data['instance_uuid'],
              'tenant_uuid': data['tenant_uuid'],
              'expiry': data['expiry'],
              'created_at': data['created_at'],
              'created_by': data['created_by'],
              'updated_at': data['updated_at'],
              'updated_by': data['updated_by']}
  
  
  class LeaseManager:
      """
      Lease Manager is the main class for mors dealing with CRUD operations for the REST API
      as well as the actual deletion of the Instances. Instance deletion and discovery is achieved
      through an object 'leasehandler'.
      """
      def __init__(self, conf):
          self.domain_mgr = DbPersistence(conf.get("DEFAULT", "db_conn"))
          self.lease_handler = get_lease_handler(conf)
          self.sleep_seconds = conf.getint("DEFAULT", "sleep_seconds")
  
      def add_tenant_lease(self, context, tenant_obj):
          logger.info("Adding tenant lease %s", tenant_obj)
          self.domain_mgr.add_tenant_lease(
              tenant_obj['tenant_uuid'],
              tenant_obj['expiry_mins'],
              context.user_id,
              datetime.utcnow())
  
      def update_tenant_lease(self, context, tenant_obj):
          logger.info("Update tenant lease %s", tenant_obj)
          self.domain_mgr.update_tenant_lease(
              tenant_obj['tenant_uuid'],
              tenant_obj['expiry_mins'],
              context.user_id,
              datetime.utcnow())
  
      def delete_tenant_lease(self, context, tenant_id):
          logger.info("Delete tenant lease %s", tenant_id)
          return self.domain_mgr.delete_tenant_lease(tenant_id)
  
      def get_tenant_leases(self, context):
          logger.debug("Getting all tenant lease")
          all_tenants = self.domain_mgr.get_all_tenant_leases()
          all_tenants = [get_tenant_lease_data(x) for x in all_tenants]
          logger.debug("Getting all tenant lease %s", all_tenants)
          return all_tenants
  
      def get_tenant_lease(self, context, tenant_id):
          data = self.domain_mgr.get_tenant_lease(tenant_id)
          logger.debug("Getting tenant lease %s", data)
          if data:
              return get_tenant_lease_data(data)
          return {}
  
      def get_tenant_and_associated_instance_leases(self, context, tenant_uuid):
          logger.debug("Getting tenant and instances leases %s", tenant_uuid)
          return {
              'tenant_lease': self.get_tenant_lease(context, tenant_uuid),
              'all_vms':
                  [get_vm_lease_data(x) for x in self.domain_mgr.get_instance_leases_by_tenant(tenant_uuid)]
          }
  
      def check_instance_lease_violation(self, instance_lease, tenant_lease):
          violation = False
          expiry = instance_lease['expiry']
          if (datetime.utcnow() + timedelta(seconds=tenant_lease['expiry_mins']*60)) < expiry:
              violation = True
          return violation
  
      def get_instance_lease(self, context, instance_id):
          data = self.domain_mgr.get_instance_lease(instance_id)
          if data:
              data = get_vm_lease_data(data)
          logger.debug("Get instance lease %s %s", instance_id, data)
          return data
  
      def add_instance_lease(self, context, tenant_uuid, instance_lease_obj):
          logger.info("Add instance lease %s", instance_lease_obj)
          tenant_lease = self.domain_mgr.get_tenant_lease(tenant_uuid)
          if not self.check_instance_lease_violation(instance_lease_obj, tenant_lease):
              self.domain_mgr.add_instance_lease(instance_lease_obj['instance_uuid'],
                                             tenant_uuid,
                                             instance_lease_obj['expiry'],
                                             context.user_id,
                                             datetime.utcnow())
          else:
              raise ValueError("Instance lease exceeds tenant lease")
  
      def update_instance_lease(self, context, tenant_uuid, instance_lease_obj):
          logger.info("Update instance lease %s", instance_lease_obj)
          tenant_lease = self.domain_mgr.get_tenant_lease(tenant_uuid)
          if not self.check_instance_lease_violation(instance_lease_obj, tenant_lease):
              self.domain_mgr.update_instance_lease(instance_lease_obj['instance_uuid'],
                                                tenant_uuid,
                                                instance_lease_obj['expiry'],
                                                context.user_id,
                                                datetime.utcnow())
          else:
              raise ValueError("Instance lease exceeds tenant lease")
  
      def delete_instance_lease(self, context, instance_uuid):
          logger.info("Delete instance lease %s", instance_uuid)
          self.domain_mgr.delete_instance_leases([instance_uuid])
  
      def start(self):
          spawn_after(self.sleep_seconds, self.run)
  
      # Could have used a generator here, would save memory but wonder if it is a good idea given the error conditions
      # This is a simple implementation which goes and deletes VMs one by one
      def _get_vms_to_delete_for_tenant(self, tenant_uuid, expiry_mins):
          vms_to_delete = []
          vm_ids_to_delete = set()
          do_not_delete = set()
          now = datetime.utcnow()
          add_seconds = timedelta(seconds=expiry_mins*60)
          instance_leases = self.get_tenant_and_associated_instance_leases(None, tenant_uuid)['all_vms']
          for i_lease in instance_leases:
              if now > i_lease['expiry']:
                  logger.info("Explicit lease for %s queueing for deletion", i_lease['instance_uuid'])
                  vms_to_delete.append(i_lease)
                  vm_ids_to_delete.add(i_lease['instance_uuid'])
              else:
                  do_not_delete.add(i_lease['instance_uuid'])
                  logger.debug("Ignoring vm, vm not expired yet %s", i_lease['instance_uuid'])
  
          tenant_vms = self.lease_handler.get_all_vms(tenant_uuid)
          for vm in tenant_vms:
              expiry_date = vm['created_at'] + add_seconds
              if now > expiry_date and vm['instance_uuid'] not in vm_ids_to_delete \
                                   and vm['instance_uuid'] not in do_not_delete:
                  logger.info("Instance %s queued up for deletion creation date %s", vm['instance_uuid'],
                              vm['created_at'])
                  vms_to_delete.append(vm)
              else:
                  logger.debug("Ignoring vm, vm not expired yet or already deleted %s, %s", vm['instance_uuid'],
                               vm['created_at'])
  
          return vms_to_delete
  
      def _delete_vms_for_tenant(self, t_lease):
          tenant_vms_to_delete = self._get_vms_to_delete_for_tenant(t_lease['tenant_uuid'], t_lease['expiry_mins'])
  
          # Keep it simple and delete them serially
          result = self.lease_handler.delete_vms(tenant_vms_to_delete)
  
          remove_from_db = []
  
          for vm_result in result.items():
              # If either the VM has been successfully deleted or has already been deleted
              # Remove from our database
              if vm_result[1] == SUCCESS_OK or vm_result[1] == ERR_NOT_FOUND:
                  remove_from_db.append(vm_result[0])
  
          if len(remove_from_db) > 0:
              logger.info("Removing vms %s from db", remove_from_db)
              self.domain_mgr.delete_instance_leases(remove_from_db)
  
      def run(self):
          # Delete the cleanup
          tenant_leases = self.domain_mgr.get_all_tenant_leases()
          for t_lease in tenant_leases:
              self._delete_vms_for_tenant(t_lease)
  
          # Sleep again for sleep_seconds
          spawn_after(self.sleep_seconds, self.run)
  