Source code for lago.subnet_lease

#
# Copyright 2014 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
#
# Refer to the README and COPYING files for full details of the license
#
"""
Module that handles the leases for the subnets of the virtual network
interfaces.


.. note:: Currently only /24 ranges are handled, and all of them under the
    192.168.MIN_SUBNET to 192.168.MAX_SUBNET ranges

The leases are stored under :class:`LEASE_DIR` as json files with the form::

    [
        "/path/to/prefix/uuid/file",
        "uuid_hash",
    ]

Where the `uuid_hash` is the 32 char uuid of the prefix (the contents of the
uuid file at the time of doing the lease)

"""
import functools
import json
import lockfile
import os

import constants
import utils

#: Lower range for the allowed subnets
MIN_SUBNET = 200
#: Upper range for the allowed subnets
MAX_SUBNET = 209

# FIXME make more robust and configurable
#: Path to the directory where the net leases are stored
LEASE_DIR = constants.SUBNET_LEASE_DIR
#: Path to the net leases lock
LOCK_FILE = os.path.join(LEASE_DIR, 'leases.lock')


[docs]def is_leasable_subnet(subnet): """ Checks if a given subnet is inside the defined provisionable range Args: subnet (str): Subnet or ip in dotted decimal format Returns: bool: True if subnet is inside the range, ``False`` otherwise """ pieces = map(int, subnet.split('.')[:-1]) return (192, 168, MIN_SUBNET) <= tuple(pieces) <= (192, 168, MAX_SUBNET)
[docs]def _validate_lease_dir_present(func): """ Decorator that will ensure that the lease dir exists, creating it if necessary """ @functools.wraps(func) def wrapper(*args, **kwargs): if not os.path.isdir(LEASE_DIR): os.makedirs(LEASE_DIR) return func(*args, **kwargs) return wrapper
[docs]def _locked(func): """ Decorator that will make sure that you have the exclusive lock for the leases """ @functools.wraps(func) def wrapper(*args, **kwargs): with lockfile.LockFile(LOCK_FILE): return func(*args, **kwargs) return wrapper
[docs]def _take_lease(path, uuid_path): """ Persist to the given leases path the prefix uuid that's in the uuid path passed Args: path (str): Path to the leases file uuid_path (str): Path to the prefix uuid Returns: None """ with open(uuid_path) as f: uuid = f.read() with open(path, 'w') as f: utils.json_dump((uuid_path, uuid), f)
[docs]def _lease_owned(path, current_uuid_path): """ Checks if the given lease is owned by the prefix whose uuid is in the given path Note: The prefix must be also in the same path it was when it took the lease Args: path (str): Path to the lease current_uuid_path (str): Path to the uuid to check ownersip of Returns: bool: ``True`` if the given lease in owned by the prefix, ``False`` otherwise """ with open(path) as f: prev_uuid_path, prev_uuid = json.load(f) with open(current_uuid_path) as f: current_uuid = f.read() return current_uuid_path == prev_uuid_path and prev_uuid == current_uuid
[docs]def _lease_valid(path): """ Checs if the given lease still has a prefix that owns it Args: path (str): Path to the lease Returns: bool: ``True`` if the uuid path in the lease still exists and is the same as the one in the lease """ with open(path) as f: uuid_path, uuid = json.load(f) if not os.path.isfile(uuid_path): return False with open(uuid_path) as f: return f.read() == uuid
@_validate_lease_dir_present @_locked
[docs]def _acquire(uuid_path): """ Lease a free network for the given uuid path Args: uuid_path (str): Path to the uuid file of a :class:`lago.Prefix` Returns: int or None: the third element of the dotted ip of the leased network or ``None`` if no lease was available .. todo:: Raise exception or something instead of returning None so the caller can handle the failure case """ for index in range(MIN_SUBNET, MAX_SUBNET + 1): lease_file = os.path.join(LEASE_DIR, '%d.lease' % index) if os.path.exists(lease_file): if _lease_valid(lease_file): continue else: os.unlink(lease_file) _take_lease(lease_file, uuid_path) return index return None
[docs]def acquire(uuid_path): """ Lease a free network for the given uuid path Args: uuid_path (str): Path to the uuid file of a :class:`lago.Prefix` Returns: str: the dotted ip of the gateway for the leased net .. todo:: _aquire might return None, this will throw a TypeError """ return '192.168.%d.1' % _acquire(uuid_path)
@_validate_lease_dir_present @_locked
[docs]def _release(index): """ Free the lease of the given subnet index Args: index (int): Third element of a dotted ip representation of the subnet, for example, for 1.2.3.4 it would be 3 Returns: None """ lease_file = os.path.join(LEASE_DIR, '%d.lease' % index) os.unlink(lease_file)
[docs]def release(subnet): """ Free the lease of the given subnet Args: subnet (str): dotted ip or network to free the lease of Returns: None """ _release(int(subnet.split('.')[2]))