Problem with merged fields from separate custom scripts

107 views
Skip to first unread message

Mahomed Hussein

unread,
Jun 2, 2020, 11:25:28 AM6/2/20
to NetBox
Hi

I am running 2.8.5 and I have two custom scripts, `add_services.py` and `create_vm.py`. The issue I have is that after running either of these scripts successfully, going into the other script appears to merge the fields from both scripts. Refreshing, clearing local cache or even trying a different browser or PC does not resolve it. The only workaround is to restart the netbox and netbox-rq services.

This is what the screen for add_services.py looks like normally



As you can see from this next screenshot, the fields from `create_vm.py` are also showing on the same page. It's not just visual either as trying to then run the script causes validation errors on the required fields.




Below is the current state of both scripts where I've tried to disable some of the fields and imports but they've not made a difference. It seems to be some sort of server side caching.

add_services.py

from django import forms
from ipam.choices import ServiceProtocolChoices
from dcim.models import Device
from virtualization.models import Cluster, VirtualMachine
from ipam.models import Service, IPAddress
from extras.models import ConfigContext, ObjectChange, Tag
from extras.scripts import (
   BaseScript,
   BooleanVar,
   ChoiceVar,
   FileVar,
   IntegerVar,
   IPAddressVar,
   IPAddressWithMaskVar,
   IPNetworkVar,
   MultiObjectVar,
   ObjectVar,
   Script,
   StringVar,
   TextVar,
)
from utilities.forms import APISelect, APISelectMultiple


class AddServiceVM(Script):
   class Meta:
       name = "Add Service to a VM"
       description = "Easily add services to a virtual server"
       field_order = [
           "server_name",
           "service_name",
           "service_port",
           "protocol_type",
           "ip4_addresses",
           "service_desc",
           "tags",
       ]

    server_name = ObjectVar(
       queryset=VirtualMachine.objects.all(),
       label="Server name",
       widget=APISelect(filter_for={"ip4_addresses": "virtual_machine_id"}),
   )

    service_name = StringVar(label="Service name", default="DNS Alias")
   service_port = StringVar(label="Service port", default="53")
   protocol_type = ChoiceVar(choices=ServiceProtocolChoices.CHOICES)
   ip4_addresses = MultiObjectVar(
       queryset=IPAddress.objects.all(),
       label="IP Address",
       widget=APISelectMultiple(
           api_url="/api/ipam/ip-addresses/", display_field="address"
       ),
   )
   service_desc = StringVar(label="Description")

    tags = MultiObjectVar(
       queryset=Tag.objects.all(),
       label="Tags",
       required=False,
       widget=APISelectMultiple(
           api_url="/api/extras/tags/", display_field="name", value_field="id"
       ),
   )

    def run(self, data, commit):
       def isNotBlank(myString):
           if myString and myString.strip():
               # myString is not None AND myString is not empty or blank
               return True
           # myString is None OR myString is empty or blank
           return False

        vm = VirtualMachine.objects.get(name=data["server_name"])

        service_descriptions = data["service_desc"].split(" ")

        for service_description in service_descriptions:
           if isNotBlank(service_description):

                newservice = Service(
                   device=None,
                   virtual_machine=vm,
                   name=data["service_name"],
                   port=data["service_port"],
                   protocol="tcp",
                   description=service_description,
               )
               newservice.save()
               for ip_addr in data["ip4_addresses"]:
                   newservice.ipaddresses.add(ip_addr)

                for tag in data["tags"]:
                   newservice.tags.add(tag)

                self.log_info("Service created for {}".format(service_description))

        self.log_success(
           "Services Created for [%s](https://<redacted>/virtualization/virtual-machines/%s/)"
           % (vm.name, vm.id)
       )

        # output = (vm,newservice)
       # return (output)


# class AddServiceDevice(Script):
#     class Meta:
#         device_type = "Physical"
#         name = "Add Service to a {}".format(device_type)
#         description = "Easily add services to a {} server".format(device_type)
#         field_order = [
#             "server_name",
#             "service_name",
#             "service_port",
#             "protocol_type",
#             "ip4_addresses",
#             "service_desc",
#             "tags",
#         ]

#     server_name = ObjectVar(
#         queryset=Device.objects.all(),
#         label="Server name",
#         widget=APISelect(filter_for={"ip4_addresses": "device_id"}),
#     )

#     service_name = StringVar(label="Service name", default="DNS Alias")
#     service_port = StringVar(label="Service port", default="53")
#     protocol_type = ChoiceVar(choices=ServiceProtocolChoices.CHOICES)
#     ip4_addresses = MultiObjectVar(
#         queryset=IPAddress.objects.all(),
#         label="IP Address",
#         widget=APISelectMultiple(
#             api_url="/api/ipam/ip-addresses/", display_field="address"
#         ),
#     )
#     service_desc = StringVar(label="Description", default="www.mydomain.com")

#     tags = MultiObjectVar(
#         queryset=Tag.objects.all(),
#         label="Tags",
#         widget=APISelectMultiple(
#             api_url="/api/extras/tags/", display_field="name", value_field="id"
#         ),
#     )

#     def run(self, data, commit):
#         def isNotBlank(myString):
#             if myString and myString.strip():
#                 # myString is not None AND myString is not empty or blank
#                 return True
#             # myString is None OR myString is empty or blank
#             return False

#         dev = Device.objects.get(name=data["server_name"])

#         service_descriptions = data["service_desc"].split(" ")

#         for service_description in service_descriptions:
#             if isNotBlank(service_description):

#                 newservice = Service(
#                     device=dev,
#                     virtual_machine=None,
#                     name=data["service_name"],
#                     port=data["service_port"],
#                     protocol="tcp",
#                     description=service_description,
#                 )
#                 newservice.save()
#                 for ip_addr in data["ip4_addresses"]:
#                     newservice.ipaddresses.add(ip_addr)

#                 for tag in data["tags"]:
#                     newservice.tags.add(tag)

#                 self.log_info("Service created for {}".format(service_description))

#         self.log_success(
#             "Services Created for [%s](https://<redacted>/virtualization/virtual-machines/%s/)"
#             % (dev.name, dev.id)
#         )

#         # output = (dev,newservice)
#         # return (output)



create_vm.py


"""
This script allows you to create a VM, an interface and primary IP address
all in one screen.

Workaround for issues:
"""

from django.contrib.contenttypes.models import ContentType

from dcim.choices import InterfaceTypeChoices
from dcim.models import DeviceRole, Platform, Interface
from django.core.exceptions import ObjectDoesNotExist
from ipam.choices import IPAddressStatusChoices
from ipam.models import IPAddress, VRF
from tenancy.models import Tenant
from virtualization.choices import VirtualMachineStatusChoices
from virtualization.models import Cluster, VirtualMachine
from extras.scripts import (
   BaseScript,
   BooleanVar,
   ChoiceVar,
   FileVar,
   IntegerVar,
   IPAddressVar,
   IPAddressWithMaskVar,
   IPNetworkVar,
   MultiObjectVar,
   ObjectVar,
   Script,
   StringVar,
   TextVar,
)
# from extras.choices import *
# from extras.filters import CustomFieldFilter, CustomFieldFilterSet
# from extras.models import (
#     CustomField,
#     CustomFieldValue,
#     CustomFieldChoice,
#     CustomFieldModel,
# )
from utilities.forms import APISelect, APISelectMultiple, DatePicker


class NewVM(Script):
   class Meta:
       name = "New VM"
       description = "Create a new VM"
       field_order = [
           "vm_name",
           "dns_name",
           "vrf",
           "primary_ip4",
           "secondary_ip4",
           "primary_ip6",
           "role",
           "status",
           "cluster",  # 'tenant',
           "platform",
           "interface_name",
           "mac_address",
           "vcpus",
           "memory",
           "disk",
           "comments",
           # "cf_last_updated",  # , 'cf_environment'
       ]

    vm_name = StringVar(label="VM name")
   dns_name = StringVar(label="DNS name", required=False)
   vrf = ObjectVar(
       label="VRF",
       queryset=VRF.objects.all(),
       required=False,
       widget=APISelect(filter_for={"secondary_ip4": "vrf_id"}),
   )
   primary_ip4 = IPAddressWithMaskVar(label="IPv4 address")
   secondary_ip4 = MultiObjectVar(
       queryset=IPAddress.objects.all(),
       label="IP Address",
       required=False,
       widget=APISelectMultiple(
           api_url="/api/ipam/ip-addresses/", display_field="address"
       ),
   )
   primary_ip6 = IPAddressWithMaskVar(label="IPv6 address", required=False)
   role = ObjectVar(DeviceRole.objects.filter(vm_role=True), required=False)
   status = ChoiceVar(
       VirtualMachineStatusChoices, default=VirtualMachineStatusChoices.STATUS_ACTIVE
   )
   cluster = ObjectVar(Cluster.objects)
   # tenant = ObjectVar(Tenant.objects, required=False)
   platform = ObjectVar(Platform.objects, required=False)
   interface_name = StringVar(default="eth0")
   mac_address = StringVar(label="MAC address", required=False)
   vcpus = IntegerVar(label="VCPUs", required=False)
   memory = IntegerVar(label="Memory (MB)", required=False)
   disk = IntegerVar(label="Disk (GB)", required=False)
   comments = TextVar(label="Comments", required=False)

    # cf_last_updated = StringVar(
   #     label="Last Updated", required=False, widget=DatePicker()
   # )

    # content_type = ContentType.objects.get_for_model(VirtualMachine)
   # custom_fields = CustomField.objects.get(obj_type=content_type, name="Env")

    # ENV_VALS = CustomFieldChoice.objects.filter(field=custom_fields).values()

    # ENV_CHOICES = ()
   # for val in ENV_VALS:
   #     ENV_CHOICES = ENV_CHOICES + ((f"{val['id']}", f"{val['value']}"),)

    # cf_environment = ChoiceVar(choices=ENV_CHOICES, label="Environment", required=False)

    def run(self, data):
       vm = VirtualMachine(
           name=data["vm_name"],
           role=data["role"],
           status=data["status"],
           cluster=data["cluster"],
           platform=data["platform"],
           vcpus=data["vcpus"],
           memory=data["memory"],
           disk=data["disk"],
           comments=data["comments"],
           tenant=data.get("tenant"),
       )
       vm.save()

        interface = Interface(
           name=data["interface_name"],
           type=InterfaceTypeChoices.TYPE_VIRTUAL,
           mac_address=data["mac_address"],
           virtual_machine=vm,
       )
       interface.save()

        def add_addr(addr, expect_family, is_primary):
           if not addr:
               return
           if is_primary:
               if addr.version != expect_family:
                   raise RuntimeError("Wrong family for %r" % addr)
           try:
               a = IPAddress.objects.get(address=addr, vrf=data.get("vrf"),)
               result = "Assigned"
           except ObjectDoesNotExist:
               a = IPAddress(address=addr, vrf=data.get("vrf"),)
               result = "Created"
           a.status = IPAddressStatusChoices.STATUS_ACTIVE
           a.dns_name = data["dns_name"]
           if a.interface:
               raise RuntimeError("Address %s is already assigned" % addr)
           a.interface = interface
           a.tenant = data.get("tenant")
           a.save()
           self.log_info("%s IP address %s %s" % (result, a.address, a.vrf or ""))
           if is_primary:
               setattr(vm, "primary_ip%d" % a.family, a)

        add_addr(data["primary_ip4"], 4, True)
       add_addr(data["primary_ip6"], 6, True)

        for ip_addr in data["secondary_ip4"]:
           add_addr(ip_addr.address, 4, False)

        # content_type = ContentType.objects.get_for_model(VirtualMachine)
       # custom_fields = CustomFieldValue.objects.filter(
       #     obj_type=content_type, obj_id=vm.id
       # )

        # cf_lastupdate = custom_fields.filter(field_id=1)
       # cf_env = custom_fields.filter(field_id=5)
       # cf_lastupdate.update_or_create(
       #     obj_id=vm.id,
       #     obj_type_id=80,
       #     field_id=1,
       #     serialized_value=data["cf_last_updated"],
       # )
       # cf_env.update_or_create(
       #     obj_id=vm.id,
       #     obj_type_id=80,
       #     field_id=5,
       #     serialized_value=data["cf_environment"],
       # )

        vm.save()
       self.log_success(
           "Created VM [%s](https://<redacted>/virtualization/virtual-machines/%s/)"
           % (vm.name, vm.id)
       )



Any help would be greatly appreciated! Thanks in advance.

Mahomed Hussein

unread,
Jun 2, 2020, 11:39:21 AM6/2/20
to NetBox
For some reason images didn't embed correctly. Here they are again.

add_services.py

add_services.png







































create_vm.py

create_vm.png


Brian Candler

unread,
Jun 2, 2020, 11:55:22 AM6/2/20
to NetBox
Ugh.  Yes that's horribly broken, I can reproduce it here:

- click on create_vm
- enter some parameters
- click on Run Script
- it rejects the entry and displays a whole load of parameters from the add_service script
- viewing a fresh create_vm shows the merged fields

For me, "systemctl restart netbox" clears it so that create_vm just shows the VM fields.

Sorry, I can't solve this for you, but good reproducible bugs are welcomed on the github tracker ;-)

Mahomed Hussein

unread,
Jun 2, 2020, 12:44:04 PM6/2/20
to NetBox
Thank you Brian for verifying that I am not going crazy (hopefully). I have logged an issue https://github.com/netbox-community/netbox/issues/4710
Reply all
Reply to author
Forward
0 new messages