Source code for slam_host.models

"""
This module provide a Host model and all associated method.
  - Host.show: method to return a dict abstraction of a Host
  - Host.create: a staticmethod to create a Host w/ some check associated to it
  - Host.update: a staticmethod to update Host field
  - Host.remove: a staticmethod to delete a Host w/ some check associated to it
  - Host.add: a staticmethod to add a IP to a Host
  - Host.get: a staticmethod to get a dict abstraction of a Host w/o instanciate it before
  - Host.search: a staticmethod to get all Host match the filter
"""
# As we use django models.Model, pylint fail to find objects method. We must disable pylint
# test E1101 (no-member)
# pylint: disable=E1101
from distutils.util import strtobool

from django.db import models
from django.core.exceptions import ValidationError, ObjectDoesNotExist
from django.db.utils import IntegrityError

from slam_core.utils import error_message, name_validator
from slam_hardware.models import Interface
from slam_network.models import Network, Address
from slam_network.exceptions import NetworkFull
from slam_domain.models import DomainEntry, Domain


[docs]class Host(models.Model): """ Host represent a association between hardware, network and domain name service - name: a name for the hosts, by default, the name fqdn of the host - addresses: a list of IP address. Should be one-to-many relation but for some mistake, it s a many-to-many relation... - interface: the MAC address of the host - network: the main network for the host (ie. where it will be put by freeradius) - creation_date: When Host has been created - dhcp: a flag to enable, disable DHCP configuration. """ name = models.CharField(max_length=150, unique=True, validators=[name_validator]) addresses = models.ManyToManyField(Address) interface = models.ForeignKey(Interface, on_delete=models.PROTECT, null=True, blank=True, unique=True) network = models.ForeignKey(Network, on_delete=models.PROTECT, null=True, blank=True) creation_date = models.DateTimeField(auto_now_add=True, null=True) dhcp = models.BooleanField(default=True)
[docs] def show(self, short=False, key=False): """ This method return a dict construction of the object. We have 3 types of output, - standard: all information about object it-self, short information about associated objects (like ForeignKey and ManyToManyField) - short: some basic information about object it-self, primary key of associated objects - key: primary key of the object :param short: if set to True, method return a short output :param key: if set to True, method return a key output. It will overwrite short param :return: """ if key: result = { 'name': self.name } elif short: result_address = [] for address in self.addresses.all(): result_address.append(address.show(key=True)) if self.interface is None: result_interface = dict() else: result_interface = self.interface.show(key=True) if self.network is None: result_network = dict() else: result_network = self.network.show(key=True) result = { 'name': self.name, 'interface': result_interface, 'addresses': result_address, 'network': result_network, } else: result_address = [] for address in self.addresses.all(): result_address.append(address.show(short=True)) if self.interface is None: result_interface = dict() else: result_interface = self.interface.show(short=True) if self.network is None: result_network = dict() else: result_network = self.network.show(key=True) result = { 'name': self.name, 'interface': result_interface, 'addresses': result_address, 'network': result_network, 'creation_date': self.creation_date, 'dhcp': self.dhcp } return result
[docs] @staticmethod def create(name, address=None, interface=None, network=None, owner=None, dns_entry=None, options=None): """ This is a custom method to create a host w/ some check like. - Interface: check if it exist and it s free. If not, create a new one. - Address: check if it exist,it s free and in the network. If no address as been provide, get a free IP from the network - NS record: A and PTR record will be created :param name: name of the Host :param address: IP address for the Host :param interface: interface associated to this Host :param network: network associated to this Host :param owner: the owner of this Host :param dns_entry: NS record for the Host :param options: Some other options like 'no_ip' to force not get IP (Host w/o IP) :return: """ interface_host = None network_host = None address_host = None owner_host = '' if options is None: options = { 'no_ip': False, 'dhcp': True } if owner is not None: owner_host = owner if network is None and\ address is None: # We need at least one of this options return error_message('host', name, 'Integrity error Address or Network should be provide') if interface is not None and\ interface != '': # If we create a host with a interface try: # We check if interface exist (we can only one @MAC interface_host = Interface.objects.get(mac_address=interface) try: # We check if interface is already attached to a host interface_host.host_set.get() # if interface exist and is attached to a host, so we can't add a new host return error_message('host', name, 'Integrity error Interface still exist !') except ObjectDoesNotExist: # If not, so we are good, the interface is free pass except ObjectDoesNotExist: # If interface not exist, we create a new one # We generate a default HW name hardware_name = '{}-{}'.format(name.split('.', 1)[0], interface.replace(':', '-')) hardware_args = { 'owner': owner_host } # We create a interface and a hardware for this interface result = Interface.create(mac_address=interface, hardware=hardware_name, args=hardware_args) if result['status'] != 'done': # If we failed to create the interface return result # We get the new interface interface_host = Interface.objects.get(mac_address=interface) if network is not None: # If we just provided network name information try: # We get the network based on network name network_host = Network.objects.get(name=network) except ObjectDoesNotExist as err: # If network doesn't exist, we return a error return error_message('host', name, err) if address is None and\ network is not None and\ not options['no_ip']: # If we didn't provide address and ask for it try: # We try to get a new IP from network address = str(network_host.get_free_ip()) except NetworkFull: # If network is full, we return a error return error_message('host', name, 'Network have not usued IP address') if address is not None: # If we provide a specific IP address try: # We get the address address_host = Address.objects.get(ip=address) if len(address_host.host_set.all()) != 0: # If address is used by another host return error_message('host', name, 'Address is used by another host') except ObjectDoesNotExist: # If address not exist, we create it # We check if address is in the right network network_host = Address.match_network(address) if network_host is None: return error_message('host', name, 'No network found for {}'.format(address)) if dns_entry is not None: # If we provide a NS record we create the address w/ it. result = Address.create(ip=address, network=network_host.name, ns_entry=dns_entry) if result['status'] != 'done': # If something go wrong return result else: # If we didn't provide a NS record, we just create the address w/o NS record. result = Address.create(ip=address, network=network_host.name) if result['status'] != 'done': return result # We get address created address_host = Address.objects.get(ip=address) args = { 'name': name, 'interface': interface_host, 'network': network_host, } if options['dhcp'] is not None: # If we provided a specific value for DHCP generation args['dhcp'] = options['dhcp'] try: host = Host(**args) host.full_clean() host.save() if address_host is not None: host.addresses.add(address_host) # We will return a dict representation and a status result = host.show() result['status'] = 'done' return result except (ObjectDoesNotExist, IntegrityError, ValidationError) as err: return error_message('host', name, err)
[docs] @staticmethod def update(name, address=None, interface=None, network=None, dns_entry=None, dhcp=None): """ This is a custom method to update a Host. Depending of options give, the rightfull field. :param name: name of the host (not used to update but to retrieve Host) :param address: IP address of the host :param interface: mac-address of the host :param network: network of the host :param dns_entry: NS record of the host :param dhcp: should DHCP be generated :return: """ try: host = Host.objects.get(name=name) if interface is not None: if interface == '': # If interface name is '' then, we want to remove interface host.interface = None else: # else, we update it. try: host.interface = Interface.objects.get(mac_address=interface) except ObjectDoesNotExist: result_interface = Interface.create(mac_address=interface, hardware='{}-{}'. format(name.split('.', 1)[0], interface.replace(':', '-'))) if result_interface['status'] == 'done': host.interface = Interface.objects.get(mac_address=interface) else: return error_message('host', name, result_interface['message']) if network is not None: # If we want to update the network, we need to get it. host.network = Network.objects.get(name=network) if dns_entry is not None: # If we want to update the NS record, we need to get. domain_entry = Domain.objects.get(name=dns_entry['domain']) host.dns_entry = DomainEntry.objects.get(name=dns_entry['ns'], domain=domain_entry) if dhcp is not None: # If we want to update DHCP flag host.dhcp = strtobool(dhcp) try: # We check the validity of the object host.full_clean() except ValidationError as err: return error_message('host', name, err) host.save() # We save it except ObjectDoesNotExist as err: # If any object we try to get not exist, we return a # error. return error_message('host', name, err) return { 'host': name, 'status': 'done' }
[docs] @staticmethod def remove(name, addresses=True, hardware=False, dns_entry=True): """ This method is a method to delete a Host. As django use a internal method called delete to delete a instanciated object, we call the method remove. :param name: name of host :param addresses: if set to True, we also delete all addresses (default: True) :param hardware: if set to True, we also delete hardware (default: False) :param dns_entry: if set to True, we also delete dns_entry (default: True) :return: """ hardware_host = None try: # We need to get the object. host = Host.objects.get(name=name) except ObjectDoesNotExist as err: # If it not exist, no reason to delete it. return error_message('host', name, err) if addresses: # If we want to remove associated Addresses. We need to store them into a # local variable as we must delete Host before deleting addresses. So we need to keep # a trace of them. addresses_host = host.addresses.all() addresses_delete = [] # addresses we need to delete for address in addresses_host: addresses_delete.append({ 'ip': address.ip, 'network': address.network, 'ns_entry': dns_entry }) if hardware: # We get the interface, far more easiest as it s a one-to-one relation hardware_host = host.interface.hardware try: # Now we can try to remove the Host host.delete() except IntegrityError as err: # If for some reason, it s not possible. return error_message('host', name, err) if addresses_delete is not None: # Now we can remove addresses for address in addresses_delete: result = Address.remove(**address) if result['status'] != 'done': # If for some reason, it's not possible return result if hardware_host is not None: # Now we remove the interface try: host.interface.hardware.delete() except IntegrityError as err: return error_message('host', name, err) return { 'host': name, 'status': 'done' }
[docs] @staticmethod def add(name, address, args=None): """ This is a custom method to add a IP to a host. :param name: the host name :param address: IP address :param args: some optional options :return: """ record = name if args is not None: try: record = args['fqdn'] except KeyError: pass fqdn = record.split('.', 1) ns = fqdn[0] domain = fqdn[1] ns_entry = { 'name': ns, 'domain': domain } try: host = Host.objects.get(name=name) except ObjectDoesNotExist as err: error_message('host', name, err) try: # get the address address = Address.objects.get(ip=address) if address.host_set.all() != 0: # If address is not free, we return a error return error_message('host', name, 'Address already used by another host') except ObjectDoesNotExist: # If address not exist, we create if network = Address.match_network(ip=address) # By geting the network associated to it. result = Address.create(address, network.name, ns_entry) if result['status'] != 'done': # If something go wrong return result address = Address.objects.get(ip=address) # We get the address created host.addresses.add(address) # We add it. return { 'status': 'done', 'host': name }
[docs] @staticmethod def get(name): """ This is a custom method to get the dict abstraction of a Host. We get a standard version of the abstraction (see show method comment). :param name: name of the host :return: """ try: host = Host.objects.get(name=name) except ObjectDoesNotExist as err: return error_message('host', name, err) result = host.show() return result
[docs] @staticmethod def search(filters=None): """ This is a custom method to get a dict abstraction of all Host on database. We get a short version of Host (see show method comment). :param filters: a dict of field as QuerySet :return: """ if filters is None: # If no filters, we get all Host hosts = Host.objects.all() else: # We suppose filter as been construct outside models class hosts = Host.objects.filter(**filters) result = [] for host in hosts: # We create the dict abstraction result.append(host.show(short=True)) return result