Skip to content

Closes: #18588: Relabel Service to Application Service #19900

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: feature
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions docs/features/ipam.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ VRF modeling in NetBox very closely follows what you find in real-world network

An often overlooked component of IPAM, NetBox also tracks autonomous system (AS) numbers and their assignment to sites. Both 16- and 32-bit AS numbers are supported, and like aggregates each ASN is assigned to an authoritative RIR.

## Service Mapping
## Application Service Mapping

NetBox models network applications as discrete service objects associated with devices and/or virtual machines, and optionally with specific IP addresses attached to those parent objects. These can be used to catalog the applications running on your network for reference by other objects or integrated tools.

To model services in NetBox, begin by creating a service template defining the name, protocol, and port number(s) on which the service listens. This template can then be easily instantiated to "attach" new services to a device or virtual machine. It's also possible to create new services by hand, without a template, however this approach can be tedious.
To model application services in NetBox, begin by creating an application service template defining the name, protocol, and port number(s) on which the service listens. This template can then be easily instantiated to "attach" new services to a device or virtual machine. It's also possible to create new application services by hand, without a template, however this approach can be tedious.
12 changes: 8 additions & 4 deletions docs/models/ipam/service.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
# Services
# Application Services

A service represents a layer seven application available on a device or virtual machine. For example, a service might be created in NetBox to represent an HTTP server running on TCP/8000. Each service may optionally be further bound to one or more specific interfaces assigned to the selected device or virtual machine.
An application service represents a layer seven application available on a device or virtual machine. For example, a service might be created in NetBox to represent an HTTP server running on TCP/8000. Each service may optionally be further bound to one or more specific interfaces assigned to the selected device or virtual machine.

To aid in the efficient creation of services, users may opt to first create a [service template](./servicetemplate.md) from which service definitions can be quickly replicated.
To aid in the efficient creation of application services, users may opt to first create an [application service template](./servicetemplate.md) from which service definitions can be quickly replicated.

!!! note "Changed in NetBox v4.4"

Previously, application services were referred to simply as "services". The name has been changed in the UI to better reflect their intended use. There is no change to the name of the model or in any programmatic NetBox APIs.

## Fields

### Parent

The parent object to which the service is assigned. This must be one of [Device](../dcim/device.md),
The parent object to which the application service is assigned. This must be one of [Device](../dcim/device.md),
[VirtualMachine](../virtualization/virtualmachine.md), or [FHRP Group](./fhrpgroup.md).

!!! note "Changed in NetBox v4.3"
Expand Down
8 changes: 6 additions & 2 deletions docs/models/ipam/servicetemplate.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Service Templates
# Application Service Templates

Service templates can be used to instantiate [services](./service.md) on [devices](../dcim/device.md) and [virtual machines](../virtualization/virtualmachine.md).
Application service templates can be used to instantiate [application services](./service.md) on [devices](../dcim/device.md) and [virtual machines](../virtualization/virtualmachine.md).

!!! note "Changed in NetBox v4.4"

Previously, application service templates were referred to simply as "service templates". The name has been changed in the UI to better reflect their intended use. There is no change to the name of the model or in any programmatic NetBox APIs.

## Fields

Expand Down
2 changes: 1 addition & 1 deletion docs/models/virtualization/vminterface.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## Interfaces

[Virtual machine](./virtualmachine.md) interfaces behave similarly to device [interfaces](../dcim/interface.md): They can be assigned to VRFs, may have IP addresses, VLANs, and services attached to them, and so on. However, given their virtual nature, they lack properties pertaining to physical attributes. For example, VM interfaces do not have a physical type and cannot have cables attached to them.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this bit here because I could not find any direct relationship between application services and VM interfaces. If this is some other concept of service, let me know and I can add it back.

[Virtual machine](./virtualmachine.md) interfaces behave similarly to device [interfaces](../dcim/interface.md): They can be assigned to VRFs, may have IP addresses, VLANs, and so on. However, given their virtual nature, they lack properties pertaining to physical attributes. For example, VM interfaces do not have a physical type and cannot have cables attached to them.

## Fields

Expand Down
2 changes: 1 addition & 1 deletion netbox/ipam/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,7 @@ class IPAddressFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFil
service_id = django_filters.ModelMultipleChoiceFilter(
field_name='services',
queryset=Service.objects.all(),
label=_('Service (ID)'),
label=_('Application Service (ID)'),
)
nat_inside_id = django_filters.ModelMultipleChoiceFilter(
field_name='nat_inside',
Expand Down
12 changes: 7 additions & 5 deletions netbox/ipam/forms/model_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,7 @@ class ServiceTemplateForm(NetBoxModelForm):
comments = CommentField()

fieldsets = (
FieldSet('name', 'protocol', 'ports', 'description', 'tags', name=_('Service Template')),
FieldSet('name', 'protocol', 'ports', 'description', 'tags', name=_('Application Service Template')),
)

class Meta:
Expand Down Expand Up @@ -794,7 +794,7 @@ class ServiceForm(NetBoxModelForm):
FieldSet(
'parent_object_type', 'parent', 'name',
InlineFields('protocol', 'ports', label=_('Port(s)')),
'ipaddresses', 'description', 'tags', name=_('Service')
'ipaddresses', 'description', 'tags', name=_('Application Service')
),
)

Expand Down Expand Up @@ -836,7 +836,7 @@ def clean(self):

class ServiceCreateForm(ServiceForm):
service_template = DynamicModelChoiceField(
label=_('Service template'),
label=_('Application Service template'),
queryset=ServiceTemplate.objects.all(),
required=False
)
Expand All @@ -848,7 +848,7 @@ class ServiceCreateForm(ServiceForm):
FieldSet('service_template', name=_('From Template')),
FieldSet('name', 'protocol', 'ports', name=_('Custom')),
),
'ipaddresses', 'description', 'tags', name=_('Service')
'ipaddresses', 'description', 'tags', name=_('Application Service')
),
)

Expand Down Expand Up @@ -877,4 +877,6 @@ def clean(self):
if not self.cleaned_data['description']:
self.cleaned_data['description'] = service_template.description
elif not all(self.cleaned_data[f] for f in ('name', 'protocol', 'ports')):
raise forms.ValidationError(_("Must specify name, protocol, and port(s) if not using a service template."))
raise forms.ValidationError(
_("Must specify name, protocol, and port(s) if not using an application service template.")
)
10 changes: 5 additions & 5 deletions netbox/ipam/models/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ class ServiceTemplate(ServiceBase, PrimaryModel):

class Meta:
ordering = ('name',)
verbose_name = _('service template')
verbose_name_plural = _('service templates')
verbose_name = _('application service template')
verbose_name_plural = _('application service templates')


class Service(ContactsMixin, ServiceBase, PrimaryModel):
Expand Down Expand Up @@ -84,7 +84,7 @@ class Service(ContactsMixin, ServiceBase, PrimaryModel):
related_name='services',
blank=True,
verbose_name=_('IP addresses'),
help_text=_("The specific IP addresses (if any) to which this service is bound")
help_text=_("The specific IP addresses (if any) to which this application service is bound")
)

clone_fields = ['protocol', 'ports', 'description', 'parent', 'ipaddresses', ]
Expand All @@ -94,5 +94,5 @@ class Meta:
models.Index(fields=('parent_object_type', 'parent_object_id')),
)
ordering = ('protocol', 'ports', 'pk') # (protocol, port) may be non-unique
verbose_name = _('service')
verbose_name_plural = _('services')
verbose_name = _('application service')
verbose_name_plural = _('application services')
2 changes: 2 additions & 0 deletions netbox/ipam/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,7 @@ class ServiceTemplateTest(APIViewTestCases.APIViewTestCase):
bulk_update_data = {
'description': 'New description',
}
graphql_base_name = 'service_template'

@classmethod
def setUpTestData(cls):
Expand Down Expand Up @@ -1197,6 +1198,7 @@ class ServiceTest(APIViewTestCases.APIViewTestCase):
bulk_update_data = {
'description': 'New description',
}
graphql_base_name = 'service'

@classmethod
def setUpTestData(cls):
Expand Down
3 changes: 3 additions & 0 deletions netbox/ipam/tests/test_filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1101,6 +1101,9 @@ class IPAddressTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = IPAddress.objects.all()
filterset = IPAddressFilterSet
ignore_fields = ('fhrpgroup',)
filter_name_map = {
'application_service': 'service',
}

@classmethod
def setUpTestData(cls):
Expand Down
4 changes: 2 additions & 2 deletions netbox/netbox/navigation/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,8 @@
label=_('Other'),
items=(
get_model_item('ipam', 'fhrpgroup', _('FHRP Groups')),
get_model_item('ipam', 'servicetemplate', _('Service Templates')),
get_model_item('ipam', 'service', _('Services')),
get_model_item('ipam', 'servicetemplate', _('Application Service Templates')),
get_model_item('ipam', 'service', _('Application Services')),
),
),
),
Expand Down
4 changes: 2 additions & 2 deletions netbox/templates/dcim/device.html
Original file line number Diff line number Diff line change
Expand Up @@ -305,11 +305,11 @@ <h2 class="card-header">{% trans "Power Utilization" %}</h2>
{% endif %}
<div class="card">
<h2 class="card-header">
{% trans "Services" %}
{% trans "Application Services" %}
{% if perms.ipam.add_service %}
<div class="card-actions">
<a href="{% url 'ipam:service_add' %}?parent_object_type={{ object|content_type_id }}&parent={{ object.pk }}" class="btn btn-ghost-primary btn-sm">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Add a service" %}
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Add an application service" %}
</a>
</div>
{% endif %}
Expand Down
2 changes: 1 addition & 1 deletion netbox/templates/ipam/ipaddress.html
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ <h2 class="card-header">{% trans "IP Address" %}</h2>
{% include 'inc/panel_table.html' with table=duplicate_ips_table heading='Duplicate IPs' panel_class='danger' %}
{% endif %}
<div class="card">
<h2 class="card-header">{% trans "Services" %}</h2>
<h2 class="card-header">{% trans "Application Services" %}</h2>
{% htmx_table 'ipam:service_list' ip_address_id=object.pk %}
</div>
{% plugin_right_page object %}
Expand Down
2 changes: 1 addition & 1 deletion netbox/templates/ipam/servicetemplate.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<div class="row mb-3">
<div class="col col-12 col-md-6">
<div class="card">
<h2 class="card-header">{% trans "Service Template" %}</h2>
<h2 class="card-header">{% trans "Application Service Template" %}</h2>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Name" %}</th>
Expand Down
4 changes: 2 additions & 2 deletions netbox/templates/virtualization/virtualmachine.html
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,11 @@ <h2 class="card-header">{% trans "Resources" %}</h2>
</div>
<div class="card">
<h2 class="card-header">
{% trans "Services" %}
{% trans "Application Services" %}
{% if perms.ipam.add_service %}
<div class="card-actions">
<a href="{% url 'ipam:service_add' %}?parent_object_type={{ object|content_type_id }}&parent={{ object.pk }}" class="btn btn-ghost-primary btn-sm">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Add a service" %}
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Add an application service" %}
</a>
</div>
{% endif %}
Expand Down
18 changes: 13 additions & 5 deletions netbox/utilities/testing/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class BaseFilterSetTests:
queryset = None
filterset = None
ignore_fields = tuple()
filter_name_map = {}

def get_m2m_filter_name(self, field):
"""
Expand All @@ -46,7 +47,13 @@ def get_filters_for_model_field(self, field):
"""
Given a model field, return an iterable of (name, class) for each filter that should be defined on
the model's FilterSet class. If the appropriate filter class cannot be determined, it will be None.
filter_name_map provides a mechanism for developers to provide an actual field name for the
filter that is being resolved, given the field's actual name.
"""
# If an alias is not present in filter_name_map, then use field.name
filter_name = self.filter_name_map.get(field.name, field.name)

# ForeignKey & OneToOneField
if issubclass(field.__class__, ForeignKey) or type(field) is OneToOneRel:

Expand All @@ -57,19 +64,20 @@ def get_filters_for_model_field(self, field):
# ForeignKeys to ObjectType need two filters: 'app.model' & PK
if field.related_model is ObjectType:
return [
(field.name, ContentTypeFilter),
(f'{field.name}_id', django_filters.ModelMultipleChoiceFilter),
(filter_name, ContentTypeFilter),
(f'{filter_name}_id', django_filters.ModelMultipleChoiceFilter),
]

# ForeignKey to an MPTT-enabled model
if issubclass(field.related_model, MPTTModel) and field.model is not field.related_model:
return [(f'{field.name}_id', TreeNodeMultipleChoiceFilter)]
return [(f'{filter_name}_id', TreeNodeMultipleChoiceFilter)]

return [(f'{field.name}_id', django_filters.ModelMultipleChoiceFilter)]
return [(f'{filter_name}_id', django_filters.ModelMultipleChoiceFilter)]

# Many-to-many relationships (forward & backward)
elif type(field) in (ManyToManyField, ManyToManyRel):
filter_name = self.get_m2m_filter_name(field)
filter_name = self.filter_name_map.get(filter_name, filter_name)

# ManyToManyFields to ObjectType need two filters: 'app.model' & PK
if field.related_model is ObjectType:
Expand All @@ -85,7 +93,7 @@ def get_filters_for_model_field(self, field):
return [('tag', TagFilter)]

# Unable to determine the correct filter class
return [(field.name, None)]
return [(filter_name, None)]

def test_id(self):
"""
Expand Down