Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/models/virtualization/virtualmachine.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ The VM's operational status.
!!! tip
Additional statuses may be defined by setting `VirtualMachine.status` under the [`FIELD_CHOICES`](../../configuration/data-validation.md#field_choices) configuration parameter.

### Start on boot

The start on boot setting from the hypervisor.

!!! tip
Additional statuses may be defined by setting `VirtualMachine.start_on_boot` under the [`FIELD_CHOICES`](../../configuration/data-validation.md#field_choices) configuration parameter.

### Site & Cluster

The [site](../dcim/site.md) and/or [cluster](./cluster.md) to which the VM is assigned.
Expand Down
4 changes: 4 additions & 0 deletions netbox/templates/virtualization/virtualmachine.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ <h2 class="card-header">{% trans "Virtual Machine" %}</h2>
<th scope="row">{% trans "Status" %}</th>
<td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
</tr>
<tr>
<th scope="row">{% trans "Start on boot" %}</th>
<td>{% badge object.get_start_on_boot_display bg_color=object.get_start_on_boot_color %}</td>
</tr>
<tr>
<th scope="row">{% trans "Role" %}</th>
<td>{{ object.role|linkify|placeholder }}</td>
Expand Down
17 changes: 9 additions & 8 deletions netbox/virtualization/api/serializers_/virtualmachines.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

class VirtualMachineSerializer(PrimaryModelSerializer):
status = ChoiceField(choices=VirtualMachineStatusChoices, required=False)
start_on_boot = ChoiceField(choices=VirtualMachineStartOnBootChoices, required=False)
site = SiteSerializer(nested=True, required=False, allow_null=True, default=None)
cluster = ClusterSerializer(nested=True, required=False, allow_null=True, default=None)
device = DeviceSerializer(nested=True, required=False, allow_null=True, default=None)
Expand All @@ -49,10 +50,10 @@ class VirtualMachineSerializer(PrimaryModelSerializer):
class Meta:
model = VirtualMachine
fields = [
'id', 'url', 'display_url', 'display', 'name', 'status', 'site', 'cluster', 'device', 'serial', 'role',
'tenant', 'platform', 'primary_ip', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'description',
'owner', 'comments', 'config_template', 'local_context_data', 'tags', 'custom_fields', 'created',
'last_updated', 'interface_count', 'virtual_disk_count',
'id', 'url', 'display_url', 'display', 'name', 'status', 'start_on_boot', 'site', 'cluster', 'device',
'serial', 'role', 'tenant', 'platform', 'primary_ip', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory',
'disk', 'description', 'owner', 'comments', 'config_template', 'local_context_data', 'tags',
'custom_fields', 'created', 'last_updated', 'interface_count', 'virtual_disk_count',
]
brief_fields = ('id', 'url', 'display', 'name', 'description')

Expand All @@ -62,10 +63,10 @@ class VirtualMachineWithConfigContextSerializer(VirtualMachineSerializer):

class Meta(VirtualMachineSerializer.Meta):
fields = [
'id', 'url', 'display_url', 'display', 'name', 'status', 'site', 'cluster', 'device', 'serial', 'role',
'tenant', 'platform', 'primary_ip', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'description',
'comments', 'config_template', 'local_context_data', 'tags', 'custom_fields', 'config_context', 'created',
'last_updated', 'interface_count', 'virtual_disk_count',
'id', 'url', 'display_url', 'display', 'name', 'status', 'start_on_boot', 'site', 'cluster', 'device',
'serial', 'role', 'tenant', 'platform', 'primary_ip', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory',
'disk', 'description', 'comments', 'config_template', 'local_context_data', 'tags', 'custom_fields',
'config_context', 'created', 'last_updated', 'interface_count', 'virtual_disk_count',
]

@extend_schema_field(serializers.JSONField(allow_null=True))
Expand Down
14 changes: 14 additions & 0 deletions netbox/virtualization/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,17 @@ class VirtualMachineStatusChoices(ChoiceSet):
(STATUS_DECOMMISSIONING, _('Decommissioning'), 'yellow'),
(STATUS_PAUSED, _('Paused'), 'orange'),
]


class VirtualMachineStartOnBootChoices(ChoiceSet):
key = 'VirtualMachine.start_on_boot'

STATUS_ON = 'on'
STATUS_OFF = 'off'
STATUS_LAST_STATE = 'laststate'

CHOICES = [
(STATUS_ON, _('On'), 'green'),
(STATUS_OFF, _('Off'), 'gray'),
(STATUS_LAST_STATE, _('Last State'), 'cyan')
]
4 changes: 4 additions & 0 deletions netbox/virtualization/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ class VirtualMachineFilterSet(
choices=VirtualMachineStatusChoices,
null_value=None
)
start_on_boot = django_filters.MultipleChoiceFilter(
choices=VirtualMachineStartOnBootChoices,
null_value=None
)
cluster_group_id = django_filters.ModelMultipleChoiceFilter(
field_name='cluster__group',
queryset=ClusterGroup.objects.all(),
Expand Down
8 changes: 7 additions & 1 deletion netbox/virtualization/forms/bulk_edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ class VirtualMachineBulkEditForm(PrimaryModelBulkEditForm):
required=False,
initial='',
)
start_on_boot = forms.ChoiceField(
label=_('Start on boot'),
choices=add_blank_choice(VirtualMachineStartOnBootChoices),
required=False,
initial='',
)
site = DynamicModelChoiceField(
label=_('Site'),
queryset=Site.objects.all(),
Expand Down Expand Up @@ -145,7 +151,7 @@ class VirtualMachineBulkEditForm(PrimaryModelBulkEditForm):

model = VirtualMachine
fieldsets = (
FieldSet('site', 'cluster', 'device', 'status', 'role', 'tenant', 'platform', 'description'),
FieldSet('site', 'cluster', 'device', 'status', 'start_on_boot', 'role', 'tenant', 'platform', 'description'),
FieldSet('vcpus', 'memory', 'disk', name=_('Resources')),
FieldSet('config_template', name=_('Configuration')),
)
Expand Down
10 changes: 8 additions & 2 deletions netbox/virtualization/forms/bulk_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ class VirtualMachineImportForm(PrimaryModelImportForm):
choices=VirtualMachineStatusChoices,
help_text=_('Operational status')
)
start_on_boot = CSVChoiceField(
label=_('Start on boot'),
choices=VirtualMachineStartOnBootChoices,
help_text=_('Start on boot in hypervisor'),
required=False,
)
site = CSVModelChoiceField(
label=_('Site'),
queryset=Site.objects.all(),
Expand Down Expand Up @@ -143,8 +149,8 @@ class VirtualMachineImportForm(PrimaryModelImportForm):
class Meta:
model = VirtualMachine
fields = (
'name', 'status', 'role', 'site', 'cluster', 'device', 'tenant', 'platform', 'vcpus', 'memory', 'disk',
'description', 'serial', 'config_template', 'comments', 'owner', 'tags',
'name', 'status', 'start_on_boot', 'role', 'site', 'cluster', 'device', 'tenant', 'platform', 'vcpus',
'memory', 'disk', 'description', 'serial', 'config_template', 'comments', 'owner', 'tags',
)


Expand Down
7 changes: 6 additions & 1 deletion netbox/virtualization/forms/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class VirtualMachineFilterForm(
FieldSet('cluster_group_id', 'cluster_type_id', 'cluster_id', 'device_id', name=_('Cluster')),
FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')),
FieldSet(
'status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'config_template_id',
'status', 'start_on_boot', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'config_template_id',
'local_context_data', 'serial', name=_('Attributes')
),
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
Expand Down Expand Up @@ -171,6 +171,11 @@ class VirtualMachineFilterForm(
choices=VirtualMachineStatusChoices,
required=False
)
start_on_boot = forms.MultipleChoiceField(
label=_('Start on boot'),
choices=VirtualMachineStartOnBootChoices,
required=False
)
platform_id = DynamicModelMultipleChoiceField(
queryset=Platform.objects.all(),
required=False,
Expand Down
8 changes: 4 additions & 4 deletions netbox/virtualization/forms/model_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ class VirtualMachineForm(TenancyForm, PrimaryModelForm):
)

fieldsets = (
FieldSet('name', 'role', 'status', 'description', 'serial', 'tags', name=_('Virtual Machine')),
FieldSet('name', 'role', 'status', 'start_on_boot', 'description', 'serial', 'tags', name=_('Virtual Machine')),
FieldSet('site', 'cluster', 'device', name=_('Site/Cluster')),
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
FieldSet('platform', 'primary_ip4', 'primary_ip6', 'config_template', name=_('Management')),
Expand All @@ -228,9 +228,9 @@ class VirtualMachineForm(TenancyForm, PrimaryModelForm):
class Meta:
model = VirtualMachine
fields = [
'name', 'status', 'site', 'cluster', 'device', 'role', 'tenant_group', 'tenant', 'platform', 'primary_ip4',
'primary_ip6', 'vcpus', 'memory', 'disk', 'description', 'serial', 'owner', 'comments', 'tags',
'local_context_data', 'config_template',
'name', 'status', 'start_on_boot', 'site', 'cluster', 'device', 'role', 'tenant_group', 'tenant',
'platform', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'description', 'serial', 'owner',
'comments', 'tags', 'local_context_data', 'config_template',
]

def __init__(self, *args, **kwargs):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.2.7 on 2025-11-05 13:36

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('virtualization', '0049_owner'),
]

operations = [
migrations.AddField(
model_name='virtualmachine',
name='start_on_boot',
field=models.CharField(default='off', max_length=32),
),
]
9 changes: 9 additions & 0 deletions netbox/virtualization/models/virtualmachines.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ class VirtualMachine(ContactsMixin, ImageAttachmentsMixin, RenderConfigMixin, Co
default=VirtualMachineStatusChoices.STATUS_ACTIVE,
verbose_name=_('status')
)
start_on_boot = models.CharField(
max_length=32,
choices=VirtualMachineStartOnBootChoices,
default=VirtualMachineStartOnBootChoices.STATUS_OFF,
verbose_name=_('start on boot'),
)
role = models.ForeignKey(
to='dcim.DeviceRole',
on_delete=models.PROTECT,
Expand Down Expand Up @@ -247,6 +253,9 @@ def save(self, *args, **kwargs):
def get_status_color(self):
return VirtualMachineStatusChoices.colors.get(self.status)

def get_start_on_boot_color(self):
return VirtualMachineStartOnBootChoices.colors.get(self.start_on_boot)

@property
def primary_ip(self):
if get_config().PREFER_IPV4 and self.primary_ip4:
Expand Down
9 changes: 6 additions & 3 deletions netbox/virtualization/tables/virtualmachines.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ class VirtualMachineTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModel
status = columns.ChoiceFieldColumn(
verbose_name=_('Status'),
)
start_on_boot = columns.ChoiceFieldColumn(
verbose_name=_('Start on boot'),
)
site = tables.Column(
verbose_name=_('Site'),
linkify=True
Expand Down Expand Up @@ -81,9 +84,9 @@ class VirtualMachineTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModel
class Meta(PrimaryModelTable.Meta):
model = VirtualMachine
fields = (
'pk', 'id', 'name', 'status', 'site', 'cluster', 'device', 'role', 'tenant', 'tenant_group', 'vcpus',
'memory', 'disk', 'primary_ip4', 'primary_ip6', 'primary_ip', 'description', 'comments', 'config_template',
'serial', 'contacts', 'tags', 'created', 'last_updated',
'pk', 'id', 'name', 'status', 'start_on_boot', 'site', 'cluster', 'device', 'role', 'tenant',
'tenant_group', 'vcpus', 'memory', 'disk', 'primary_ip4', 'primary_ip6', 'primary_ip', 'description',
'comments', 'config_template', 'serial', 'contacts', 'tags', 'created', 'last_updated',
)
default_columns = (
'pk', 'name', 'status', 'site', 'cluster', 'role', 'tenant', 'vcpus', 'memory', 'disk', 'primary_ip',
Expand Down
4 changes: 3 additions & 1 deletion netbox/virtualization/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,8 @@ def setUpTestData(cls):
name='Virtual Machine 3',
site=sites[0],
cluster=clusters[0],
local_context_data={'C': 3}
local_context_data={'C': 3},
start_on_boot=VirtualMachineStartOnBootChoices.STATUS_ON,
),
)
VirtualMachine.objects.bulk_create(virtual_machines)
Expand All @@ -235,6 +236,7 @@ def setUpTestData(cls):
{
'name': 'Virtual Machine 7',
'cluster': clusters[2].pk,
'start_on_boot': VirtualMachineStartOnBootChoices.STATUS_ON,
},
]

Expand Down
10 changes: 8 additions & 2 deletions netbox/virtualization/tests/test_filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,8 @@ def setUpTestData(cls):
memory=2,
disk=2,
description='foobar2',
serial='222-bbb'
serial='222-bbb',
start_on_boot=VirtualMachineStartOnBootChoices.STATUS_OFF,
),
VirtualMachine(
name='Virtual Machine 3',
Expand All @@ -363,7 +364,8 @@ def setUpTestData(cls):
vcpus=3,
memory=3,
disk=3,
description='foobar3'
description='foobar3',
start_on_boot=VirtualMachineStartOnBootChoices.STATUS_ON,
),
)
VirtualMachine.objects.bulk_create(vms)
Expand Down Expand Up @@ -430,6 +432,10 @@ def test_status(self):
params = {'status': [VirtualMachineStatusChoices.STATUS_ACTIVE, VirtualMachineStatusChoices.STATUS_STAGED]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

def test_start_on_boot(self):
params = {'start_on_boot': [VirtualMachineStartOnBootChoices.STATUS_ON]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)

def test_cluster_group(self):
groups = ClusterGroup.objects.all()[:2]
params = {'cluster_group_id': [groups[0].pk, groups[1].pk]}
Expand Down
2 changes: 2 additions & 0 deletions netbox/virtualization/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ def setUpTestData(cls):
'platform': platforms[1].pk,
'name': 'Virtual Machine X',
'status': VirtualMachineStatusChoices.STATUS_STAGED,
'start_on_boot': VirtualMachineStartOnBootChoices.STATUS_ON,
'role': roles[1].pk,
'primary_ip4': None,
'primary_ip6': None,
Expand Down Expand Up @@ -309,6 +310,7 @@ def setUpTestData(cls):
'memory': 65535,
'disk': 8000,
'comments': 'New comments',
'start_on_boot': VirtualMachineStartOnBootChoices.STATUS_OFF,
}

@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
Expand Down