-
Notifications
You must be signed in to change notification settings - Fork 563
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3884 from allegro/add_auto_hostname
Add auto hostname
- Loading branch information
Showing
7 changed files
with
205 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import re | ||
from typing import Union | ||
|
||
from django.utils.translation import ugettext_lazy as _ | ||
|
||
from ralph.data_center.models import DataCenterAsset | ||
|
||
|
||
def assign_management_hostname_and_ip(modeladmin, request, queryset): | ||
for dca in queryset: | ||
try: | ||
if not modeladmin.has_change_permission(request, obj=dca): | ||
raise RuntimeError("insufficient permissions") | ||
if not dca.rack.server_room.data_center.management_hostname_suffix: | ||
raise RuntimeError("dc doesn't have hostname suffix configured") | ||
if not dca.rack.server_room.data_center.management_ip_prefix: | ||
raise RuntimeError("dc doesn't have IP prefix configured") | ||
try: | ||
rack_number_int = int(re.match(r'.*?(\d+).*?', dca.rack.name).groups()[0]) | ||
rack_number = '%03d' % rack_number_int # type: str | ||
except: # noqa | ||
raise RuntimeError(f"invalid rack name {dca.rack.name}") | ||
|
||
hostname = _infer_hostname(dca, rack_number) | ||
if not hostname: | ||
raise RuntimeError("couldn't infer management hostname") | ||
|
||
if dca.slot_no: # blade server | ||
dca.management_hostname = hostname | ||
modeladmin.message_user( | ||
request, f"Updated management hostname for asset id: {dca.id}", level="INFO" | ||
) | ||
else: | ||
ip = _infer_ip(dca, rack_number) # others (i.e. server rack) | ||
if ip: | ||
dca.management_ip = ip | ||
dca.management_hostname = hostname | ||
modeladmin.message_user( | ||
request, | ||
f"Updated management hostname and IP for asset id: {dca.id}", level="INFO" | ||
) | ||
raise RuntimeError("unknown error") | ||
except Exception as e: # noqa | ||
modeladmin.message_user(request, f"Can't update asset id: {dca.id}: {e}", level="ERROR") | ||
return | ||
|
||
|
||
assign_management_hostname_and_ip.short_description = _("Assign management hostname and IP") | ||
|
||
|
||
def _infer_hostname(asset: DataCenterAsset, rack_number: str) -> Union[str, None]: | ||
dc = asset.rack.server_room.data_center | ||
hostname_suffix = dc.management_hostname_suffix | ||
asset_position = asset.position | ||
asset_slot = asset.slot_no | ||
if dc and hostname_suffix and asset_position: | ||
if asset_slot is not None: | ||
return f"rack{rack_number}-{asset_position}u-bay{asset_slot}-mgmt.{hostname_suffix}" | ||
else: | ||
return f"rack{rack_number}-{asset_position}u-mgmt.{hostname_suffix}" | ||
else: | ||
return None | ||
|
||
|
||
def _infer_ip(asset: DataCenterAsset, rack_number: str) -> Union[str, None]: | ||
try: | ||
ip_prefix = asset.rack.server_room.data_center.management_ip_prefix | ||
# invert the numbers to fit into ip 3rd octet e.g. 503 -> X.X.053.X | ||
# this should always be ok because of rack naming conventions | ||
# convert to int to remove zeros at the beginning | ||
rack_ip_part = int(rack_number[1] + rack_number[0] + rack_number[2]) | ||
assert int(rack_ip_part) <= 255 | ||
except: # noqa | ||
raise RuntimeError(f"invalid rack name {asset.rack.name}") | ||
|
||
try: | ||
position_ip_part = asset.position + 200 # a magic number | ||
if ip_prefix and rack_ip_part and position_ip_part: | ||
return f"{ip_prefix}.{rack_ip_part}.{position_ip_part}" | ||
except: # noqa | ||
raise RuntimeError(f"can't infer management IP address") |
23 changes: 23 additions & 0 deletions
23
src/ralph/data_center/migrations/0035_auto_20250225_1404.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Generated by Django 2.2.28 on 2025-02-25 14:04 | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('data_center', '0034_auto_20240628_1207'), | ||
] | ||
|
||
operations = [ | ||
migrations.AddField( | ||
model_name='datacenter', | ||
name='management_hostname_suffix', | ||
field=models.CharField(blank=True, max_length=256, null=True, verbose_name='management hostname suffix'), | ||
), | ||
migrations.AddField( | ||
model_name='datacenter', | ||
name='management_ip_prefix', | ||
field=models.CharField(blank=True, help_text='First 16 bits e.g. 12.345', max_length=256, null=True, verbose_name='management IP suffix'), | ||
), | ||
] |
18 changes: 18 additions & 0 deletions
18
src/ralph/data_center/migrations/0036_auto_20250228_0912.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# Generated by Django 2.2.28 on 2025-02-28 09:12 | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('data_center', '0035_auto_20250225_1404'), | ||
] | ||
|
||
operations = [ | ||
migrations.AlterField( | ||
model_name='datacenter', | ||
name='management_ip_prefix', | ||
field=models.CharField(blank=True, help_text='First 16 bits e.g. 12.345', max_length=256, null=True, verbose_name='management IP prefix'), | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,26 @@ | ||
from unittest import mock | ||
|
||
from django.contrib.admin import AdminSite | ||
from django.contrib.auth import get_user_model | ||
from django.contrib.messages.storage.fallback import FallbackStorage | ||
from django.core import mail | ||
from django.db import connection, transaction | ||
from django.test import override_settings, RequestFactory, TransactionTestCase | ||
from django.urls import reverse | ||
|
||
from ralph.accounts.tests.factories import UserFactory | ||
from ralph.assets.tests.factories import ( | ||
ServiceEnvironmentFactory, | ||
ServiceFactory | ||
) | ||
from ralph.data_center.admin import DataCenterAssetAdmin | ||
from ralph.data_center.models import DataCenterAsset | ||
from ralph.data_center.tests.factories import ( | ||
DataCenterAssetFactory, | ||
RackFactory | ||
DataCenterAssetFullFactory, | ||
DataCenterFactory, | ||
RackFactory, | ||
ServerRoomFactory | ||
) | ||
from ralph.lib.custom_fields.models import ( | ||
CustomField, | ||
|
@@ -193,3 +200,61 @@ def test_if_host_update_is_published_to_hermes_when_dca_is_updated_through_gui( | |
self.assertEqual(publish_mock.call_count, 1) | ||
# check if on_commit callbacks are removed from current db connections | ||
self.assertEqual(connection.run_on_commit, []) | ||
|
||
class DataCenterAssetAdminAssignManagementHostnameTest(TransactionTestCase): | ||
def setUp(self): | ||
self.user = get_user_model().objects.create_superuser( | ||
username="root", password="password", email="[email protected]" | ||
) | ||
result = self.client.login(username="root", password="password") | ||
self.assertEqual(result, True) | ||
self.factory = RequestFactory() | ||
|
||
dc = DataCenterFactory( | ||
management_hostname_suffix = "dc1.test", | ||
management_ip_prefix = "12.34" | ||
) | ||
room = ServerRoomFactory(data_center=dc) | ||
rack = RackFactory(name="Rack 123", server_room=room) | ||
self.dca = DataCenterAssetFullFactory(rack=rack, position=18) # type: DataCenterAsset | ||
self.dca.management_hostname = None | ||
self.dca.management_ip = None | ||
self.dca.save() | ||
|
||
def build_request(self, dca): | ||
request = self.factory.post(reverse('admin:data_center_datacenterasset_changelist'), { | ||
'action': 'assign_mgmt_hostname', | ||
'_selected_action': [dca.id], | ||
}) | ||
request.user = self.user | ||
setattr(request, 'session', 'session') | ||
messages = FallbackStorage(request) | ||
setattr(request, '_messages', messages) | ||
return request | ||
|
||
def test_superuser_can_assign_mgmt_hostname_and_ip(self): | ||
admin = DataCenterAssetAdmin(DataCenterAsset, admin_site=AdminSite()) | ||
request = self.build_request(self.dca) | ||
admin.assign_mgmt_hostname(request, DataCenterAsset.objects.filter(pk=self.dca.id)) | ||
self.assertEqual(self.dca.management_hostname, 'rack123-18u-mgmt.dc1.test') | ||
self.assertEqual(self.dca.management_ip, '12.34.213.218') | ||
|
||
def test_superuser_can_assign_mgmt_hostname_for_server_blade(self): | ||
admin = DataCenterAssetAdmin(DataCenterAsset, admin_site=AdminSite()) | ||
self.dca.slot_no = 33 | ||
# we need to have IP first before setting hostname, this can be whatever | ||
self.dca.management_ip = '10.15.20.25' | ||
self.dca.save() | ||
request = self.build_request(self.dca) | ||
admin.assign_mgmt_hostname(request, DataCenterAsset.objects.filter(pk=self.dca.id)) | ||
self.assertEqual(self.dca.management_hostname, 'rack123-18u-bay33-mgmt.dc1.test') | ||
self.assertEqual(self.dca.management_ip, '10.15.20.25') | ||
|
||
def test_cant_assign_mgmt_hostname_for_server_blade_if_no_ip(self): | ||
admin = DataCenterAssetAdmin(DataCenterAsset, admin_site=AdminSite()) | ||
self.dca.slot_no = 33 | ||
self.dca.save() | ||
request = self.build_request(self.dca) | ||
admin.assign_mgmt_hostname(request, DataCenterAsset.objects.filter(pk=self.dca.id)) | ||
self.assertEqual(self.dca.management_hostname, '') | ||
self.assertEqual(self.dca.management_ip, '') |