Skip to content

Commit

Permalink
Merge pull request #3295 from m1kra/fix_duplicate_hostname
Browse files Browse the repository at this point in the history
Fix duplicate hostname
  • Loading branch information
MarekBleschke committed Aug 8, 2018
2 parents 2cecb84 + 29212c5 commit f51b400
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 3 deletions.
10 changes: 10 additions & 0 deletions src/ralph/networks/forms.py
Expand Up @@ -121,6 +121,15 @@ def _validate_ip_uniquness(self, address):
params={'ip': address},
)

def _validate_hostame_uniqueness_in_dc(self):
address = self.cleaned_data['address']
new_hostname = self.cleaned_data['hostname']
if not self.ip or self.ip.address != address:
ip = IPAddress(address=address, hostname=new_hostname)
else:
ip = self.ip
ip.validate_hostname_uniqueness_in_dc(new_hostname)

def clean_address(self):
if self._dhcp_expose_should_lock_fields:
# if address is locked, just return current address
Expand Down Expand Up @@ -164,6 +173,7 @@ def clean_dhcp_expose(self):
raise ValidationError(
_('Cannot expose in DHCP without MAC address'),
)
self._validate_hostame_uniqueness_in_dc()
return dhcp_expose

def _validate_mac_address(self):
Expand Down
28 changes: 28 additions & 0 deletions src/ralph/networks/models/networks.py
Expand Up @@ -662,6 +662,33 @@ class Meta:
def __str__(self):
return self.address

def _hostname_is_unique_in_dc(self, hostname, dc):
from ralph.dhcp.models import DHCPEntry
entries_with_hostname = DHCPEntry.objects.filter(
hostname=hostname,
network__network_environment__data_center=dc
)
if self.pk:
entries_with_hostname = entries_with_hostname.exclude(pk=self.pk)
return not entries_with_hostname.exists()

def validate_hostname_uniqueness_in_dc(self, hostname):
network = self.get_network()
if network and network.network_environment:
dc = network.network_environment.data_center
if not self._hostname_is_unique_in_dc(hostname, dc):
raise ValidationError(
'Hostname "{hostname}" is already exposed in DHCP in {dc}.'
.format(
hostname=self.hostname, dc=dc
)
)

def _validate_hostname_uniqueness_in_dc(self):
if not self.dhcp_expose:
return
self.validate_hostname_uniqueness_in_dc(self.hostname)

def _validate_expose_in_dhcp_and_mac(self):
if (
(not self.ethernet_id or (self.ethernet and not self.ethernet.mac)) and # noqa
Expand Down Expand Up @@ -708,6 +735,7 @@ def clean(self):
self._validate_expose_in_dhcp_and_mac,
self._validate_expose_in_dhcp_and_hostname,
self._validate_change_when_exposing_in_dhcp,
self._validate_hostname_uniqueness_in_dc,
]:
try:
validator()
Expand Down
104 changes: 103 additions & 1 deletion src/ralph/networks/tests/test_forms.py
Expand Up @@ -2,8 +2,13 @@
from django.test import RequestFactory

from ralph.assets.models.components import Ethernet, EthernetSpeed
from ralph.data_center.tests.factories import DataCenterFactory
from ralph.networks.models import IPAddress
from ralph.networks.tests.factories import IPAddressFactory
from ralph.networks.tests.factories import (
IPAddressFactory,
NetworkEnvironmentFactory,
NetworkFactory
)
from ralph.tests import RalphTestCase
from ralph.tests.models import PolymorphicTestModel

Expand Down Expand Up @@ -395,6 +400,103 @@ def test_dhcp_expose_for_new_record_should_pass(self):
self.assertEqual(ip.ethernet.label, 'eth10')
self.assertEqual(ip.ethernet.base_object.pk, self.obj1.pk)

def test_dhcp_expose_for_new_record_duplicate_hostname_should_not_pass(self): # noqa
network = NetworkFactory(
address='127.0.0.0/24',
network_environment=NetworkEnvironmentFactory(
data_center=DataCenterFactory(
name='DC1'
)
)
)
self.test_dhcp_expose_for_new_record_should_pass() # generate duplicate
obj2 = PolymorphicTestModel.objects.create(hostname='xyz')
inline_data = {
'TOTAL_FORMS': 2,
'INITIAL_FORMS': 1,
'0-id': self.eth1.id,
'0-base_object': obj2.id,
'0-mac': 'ff:ff:ff:ff:ff:ff',
'0-label': 'eth10',

'1-base_object': obj2.id,
'1-hostname': self.ip1.hostname,
'1-address': '127.0.0.3',
'1-mac': '11:11:11:11:11:11',
'1-label': 'eth10',
'1-dhcp_expose': 'on',
}
data = {
'hostname': obj2.hostname,
'id': obj2.id,
}
data.update(self._prepare_inline_data(inline_data))
response = self.client.post(
obj2.get_absolute_url(),
data,
follow=True
)
self.assertEqual(response.status_code, 200)
msg = 'Hostname "{hostname}" is already exposed in DHCP in {dc}'.format( # noqa
hostname=self.ip1.hostname,
dc=network.network_environment.data_center
)
self.assertIn('errors', response.context_data)
self.assertTrue(
any([msg in err for err in response.context_data['errors']])
)

def test_dhcp_expose_for_existing_record_duplicate_hostname_should_not_pass(self): # noqa
network = NetworkFactory(
address='192.168.0.0/24',
network_environment=NetworkEnvironmentFactory(
data_center=DataCenterFactory(
name='DC1'
)
)
)
name = 'some_hostname'
ip = IPAddressFactory(
dhcp_expose=True,
hostname=name,
address='192.168.0.7'
).save()
self.ip1.hostname = name
self.ip1.address = '192.168.0.12'
self.ip1.save()
inline_data = {
'TOTAL_FORMS': 2,
'INITIAL_FORMS': 1,
'0-id': self.eth1.id,
'0-base_object': self.obj1.id,
'0-label': 'eth10',

'1-base_object': self.obj1.id,
'1-hostname': name,
'1-address': '192.168.0.33',
'1-mac': '10:10:10:10:10:10',
'1-label': 'eth10',
'1-dhcp_expose': 'on',
}
data = {
'hostname': self.obj1.hostname,
'id': self.obj1.id,
}
data.update(self._prepare_inline_data(inline_data))
response = self.client.post(
self.obj1.get_absolute_url(),
data,
follow=True
)
msg = 'Hostname "{hostname}" is already exposed in DHCP in {dc}'.format( # noqa
hostname=self.ip1.hostname,
dc=network.network_environment.data_center
)
self.assertIn('errors', response.context_data)
self.assertTrue(
any([msg in err for err in response.context_data['errors']])
)

def test_dhcp_expose_for_existing_record_should_pass(self):
inline_data = {
'TOTAL_FORMS': 1,
Expand Down
110 changes: 108 additions & 2 deletions src/ralph/networks/tests/test_models.py
Expand Up @@ -11,15 +11,17 @@
from ralph.assets.tests.factories import EthernetFactory
from ralph.data_center.tests.factories import (
ClusterFactory,
DataCenterAssetFactory
DataCenterAssetFactory,
DataCenterFactory
)
from ralph.networks.admin import NetworkAdmin
from ralph.networks.filters import NetworkClassFilter
from ralph.networks.models.choices import IPAddressStatus
from ralph.networks.models.networks import IPAddress, Network
from ralph.networks.tests.factories import (
IPAddressFactory,
NetworkEnvironmentFactory
NetworkEnvironmentFactory,
NetworkFactory
)
from ralph.tests import RalphTestCase
from ralph.virtual.tests.factories import VirtualServerFactory
Expand Down Expand Up @@ -568,6 +570,110 @@ def test_change_ethernet_with_dhcp_exposition_should_not_pass(self): # noqa
):
self.ip.clean()

def test_duplicate_hostname_with_dhcp_exposition_should_not_pass(self):
name = 'random.hostname.net'
network = NetworkFactory(
address='192.168.0.0/24',
network_environment=NetworkEnvironmentFactory()
)
ip = IPAddressFactory(
hostname=name, address='192.168.0.1',
dhcp_expose=True
)
self.ip.hostname = name
self.ip.address = '192.168.0.2'
self.ip.dhcp_expose = True
with self.assertRaises(
ValidationError,
msg='Hostname "{hostname}" is already exposed in DHCP in {dc}.'.
format(
hostname=name,
dc=network.network_environment.data_center
)
):
self.ip.validate_hostname_uniqueness_in_dc(name)
with self.assertRaises(
ValidationError,
msg='Hostname "{hostname}" is already exposed in DHCP in {dc}.'.
format(
hostname=name,
dc=network.network_environment.data_center
)
):
self.ip.clean()

def test_duplicate_hostname_in_different_networks_in_same_dc_should_not_pass(self): # noqa
name = 'random.hostname.net'
network = NetworkFactory(
address='192.168.0.0/24',
network_environment=NetworkEnvironmentFactory(
data_center=DataCenterFactory(
name='DC1'
)
)
)
network1 = NetworkFactory(
address='1.1.0.0/24',
network_environment=NetworkEnvironmentFactory(
data_center=DataCenterFactory(
name='DC1'
)
)
)
ip = IPAddressFactory(
hostname=name, address='192.168.0.1',
dhcp_expose=True
)
self.ip.hostname = name
self.ip.address = '1.1.0.2'
self.ip.dhcp_expose = True
with self.assertRaises(
ValidationError,
msg='Hostname "{hostname}" is already exposed in DHCP in {dc}.'.
format(
hostname=name,
dc=network.network_environment.data_center
)
):
self.ip.validate_hostname_uniqueness_in_dc(name)
with self.assertRaises(
ValidationError,
msg='Hostname "{hostname}" is already exposed in DHCP in {dc}.'.
format(
hostname=name,
dc=network1.network_environment.data_center
)
):
self.ip.clean()

def test_duplicate_hostnames_in_different_dcs_should_pass(self):
name = 'random.hostname.net'
network1 = NetworkFactory(
address='1.1.0.0/24',
network_environment=NetworkEnvironmentFactory(
data_center=DataCenterFactory(
name='DC1'
)
)
)
network2 = NetworkFactory(
address='192.168.0.0/24',
network_environment=NetworkEnvironmentFactory(
data_center=DataCenterFactory(
name='DC2'
)
)
)
ip = IPAddressFactory(
hostname=name, address='1.1.0.1',
dhcp_expose=True
)
self.ip.address = '192.168.0.1'
self.ip.hostname = name
self.ip.dhcp_expose = True
self.ip.validate_hostname_uniqueness_in_dc(name)
self.ip.clean()


class TestNetworkClassFilter(RalphTestCase):

Expand Down

0 comments on commit f51b400

Please sign in to comment.