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
from . import config
from . import utils
#: Lower range for the allowed subnets
MIN_SUBNET = 200
#: Upper range for the allowed subnets
MAX_SUBNET = 209
[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):
lease_dir = config.get('lease_dir')
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):
lease_dir = config.get('lease_dir')
lock_file = os.path.join(lease_dir, 'leases.lock')
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
"""
lease_dir = config.get('lease_dir')
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_dir = config.get('lease_dir')
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]))