Exemple #1
0
 def refreshall_from_xml(self, xml, refresh_children=False, variables=None):
     if len(xml) == 0:
         return []
     if variables is not None:
         return super(Firewall,
                      self).refreshall_from_xml(xml, refresh_children,
                                                variables)
     op_vars = (
         Var("serial"),
         Var("ip-address", "management_ip"),
         Var("sw-version", "version"),
         Var("multi-vsys", vartype="bool"),
         Var("vsys_id", "vsys", default="vsys1"),
         Var("vsys_name"),
         Var("ha/state/peer/serial", "serial_ha_peer"),
         Var("connected", "state.connected"),
     )
     if len(xml[0]) > 1:
         # This is a 'show devices' op command
         firewall_instances = super(Firewall, self).refreshall_from_xml(
             xml, refresh_children=False, variables=op_vars)
         # Add system settings to firewall instances
         for fw in firewall_instances:
             entry = xml.find("entry[@name='%s']" % fw.serial)
             system = fw.find_or_create(None, device.SystemSettings)
             system.hostname = entry.findtext("hostname")
             system.ip_address = entry.findtext("ip-address")
             # Add state
             fw.state.connected = yesno(entry.findtext("connected"))
             fw.state.unsupported_version = yesno(
                 entry.findtext("unsupported-version"))
     else:
         # This is a config command
         # For each vsys, instantiate a new firewall
         firewall_instances = []
         all_serial = xml.findall("entry")
         for entry in all_serial:
             all_vsys = entry.findall("vsys/entry")
             if all_vsys:
                 for vsys in all_vsys:
                     firewall_instances.append(
                         Firewall(serial=entry.get("name"),
                                  vsys=vsys.get("name")))
             else:
                 firewall_instances.append(
                     Firewall(serial=entry.get("name")))
     return firewall_instances
 def refreshall_from_xml(self, xml, refresh_children=False, variables=None):
     if len(xml) == 0:
         return []
     if variables is not None:
         return super(Firewall, self).refreshall_from_xml(
             xml, refresh_children, variables)
     op_vars = (
         Var("serial"),
         Var("ip-address", "management_ip"),
         Var("sw-version", "version"),
         Var("multi-vsys", vartype="bool"),
         Var("vsys_id", "vsys", default="vsys1"),
         Var("vsys_name"),
         Var("ha/state/peer/serial", "serial_ha_peer"),
         Var("connected", "state.connected"),
     )
     if len(xml[0]) > 1:
         # This is a 'show devices' op command
         firewall_instances = super(Firewall, self).refreshall_from_xml(
             xml, refresh_children=False, variables=op_vars)
         # Add system settings to firewall instances
         for fw in firewall_instances:
             entry = xml.find("entry[@name='%s']" % fw.serial)
             system = fw.find_or_create(None, device.SystemSettings)
             system.hostname = entry.findtext("hostname")
             system.ip_address = entry.findtext("ip-address")
             # Add state
             fw.state.connected = yesno(entry.findtext("connected"))
             fw.state.unsupported_version = yesno(entry.findtext("unsupported-version"))
     else:
         # This is a config command
         # For each vsys, instantiate a new firewall
         firewall_instances = []
         all_serial = xml.findall("entry")
         for entry in all_serial:
             all_vsys = entry.findall("vsys/entry")
             if all_vsys:
                 for vsys in all_vsys:
                     firewall_instances.append(Firewall(
                         serial=entry.get("name"), vsys=vsys.get("name")))
             else:
                 firewall_instances.append(Firewall(
                     serial=entry.get("name")))
     return firewall_instances
Exemple #3
0
    def refresh_devices(self,
                        devices=(),
                        only_connected=False,
                        expand_vsys=True,
                        include_device_groups=True,
                        add=False,
                        running_config=False):
        """Refresh device groups and devices using config and operational commands

        Uses operational command in addition to configuration to gather as much information
        as possible about Panorama connected devices. The operational commands used are
        'show devices all/connected' and 'show devicegroups'.

        Information gathered about each device includes:

        - management IP address (can be different from hostname)
        - serial
        - version
        - high availability peer releationships
        - panorama connection status
        - device-group sync status

        Args:
            devices (list): Limit refresh to these serial numbers
            only_connected (bool): Ignore devices that are not 'connected' to Panorama (Default: False)
            expand_vsys (bool): Instantiate a Firewall object for every Vsys (Default: True)
            include_device_groups (bool): Instantiate :class:`pandevice.panorama.DeviceGroup` objects with Firewall
                objects added to them.
            add (bool): Add the new tree of instantiated DeviceGroup and Firewall objects to the Panorama config tree.
                Warning: This removes all current DeviceGroup and Firewall objects from the configuration tree, and all
                their children, so it is typically done before building a configuration tree. (Default: False)
            running_config (bool): Refresh devices from the running configuration (Default: False)

        Returns:
            list: If 'include_device_groups' is True, returns a list containing new DeviceGroup instances which
            contain new Firewall instances. Any Firewall that is not in a device-group is in the list with the
            DeviceGroup instances.
            If 'include_device_groups' is False, returns a list containing new Firewall instances.

        """
        logger.debug(self.id + ": refresh_devices called")
        try:
            # Test if devices is iterable
            test_iterable = iter(devices)
        except TypeError:
            # This probably means a single device was passed in, not an iterable.
            # Convert to an iterable with a single item.
            devices = (devices, )
        # Remove None from list of devices
        devices = [x for x in devices if x is not None]
        # Get the list of managed devices
        if only_connected:
            cmd = "show devices connected"
        else:
            cmd = "show devices all"
        devices_xml = self.op(cmd)
        devices_xml = devices_xml.find("result/devices")

        # Filter to only requested devices
        if devices:
            filtered_devices_xml = ET.Element("devices")
            for device in devices:
                serial = str(device)
                if serial is None:
                    continue
                entry = devices_xml.find("entry[@name='%s']" % serial)
                if entry is None:
                    if only_connected:
                        raise err.PanNotConnectedOnPanorama(
                            "Can't find device with serial %s attached and connected"
                            " to Panorama at %s" % (serial, self.id))
                    else:
                        raise err.PanNotAttachedOnPanorama(
                            "Can't find device with serial %s attached to Panorama at %s"
                            % (serial, self.id))
                multi_vsys = yesno(entry.findtext("multi-vsys"))
                try:
                    vsys = device.vsys
                except AttributeError:
                    continue
                # Create entry if needed
                if filtered_devices_xml.find(
                        "entry[@name='%s']" % serial) is None:
                    entry_copy = deepcopy(entry)
                    # If looking for specific vsys, erase all vsys in filtered entry
                    if vsys != "shared" and vsys is not None:
                        entry_copy.remove(entry_copy.find("vsys"))
                        ET.SubElement(entry_copy, "vsys")
                    filtered_devices_xml.append(entry_copy)
                # Get specific vsys
                if vsys != "shared" and vsys is not None:
                    vsys_entry = entry.find("vsys/entry[@name='%s']" % vsys)
                    if vsys_entry is None:
                        raise err.PanNotAttachedOnPanorama(
                            "Can't find device with serial %s and"
                            " vsys %s attached to Panorama at %s" %
                            (serial, vsys, self.id))
                    vsys_section = filtered_devices_xml.find(
                        "entry[@name='%s']/vsys" % serial)
                    vsys_section.append(vsys_entry)
            devices_xml = filtered_devices_xml

        # Manipulate devices_xml so each vsys is a separate device
        if expand_vsys:
            original_devices_xml = deepcopy(devices_xml)
            devices_xml = ET.Element("devices")
            for entry in original_devices_xml:
                serial = entry.findtext("serial")
                for vsys_entry in entry.findall("vsys/entry"):
                    new_vsys_device = deepcopy(entry)
                    new_vsys_device.set("name", serial)
                    ET.SubElement(new_vsys_device,
                                  "vsys_id").text = vsys_entry.get("name")
                    ET.SubElement(
                        new_vsys_device,
                        "vsys_name").text = vsys_entry.findtext("display-name")
                    devices_xml.append(new_vsys_device)

        # Create firewall instances
        tmp_fw = self.FIREWALL_CLASS()
        firewall_instances = tmp_fw.refreshall_from_xml(
            devices_xml, refresh_children=not expand_vsys)

        if not include_device_groups:
            if add:
                self.removeall(self.FIREWALL_CLASS)
                self.extend(firewall_instances)
            return firewall_instances

        # Create device-groups

        # Get the list of device groups from configuration XML
        api_action = self.xapi.show if running_config else self.xapi.get
        devicegroup_configxml = api_action(
            "/config/devices/entry[@name='localhost.localdomain']/device-group"
        )
        devicegroup_configxml = devicegroup_configxml.find(
            "result/device-group")

        # Get the list of device groups from operational commands
        devicegroup_opxml = self.op("show devicegroups")
        devicegroup_opxml = devicegroup_opxml.find("result/devicegroups")

        # Combine the config XML and operational command XML to get a complete picture
        # of the device groups
        for dg_entry in devicegroup_configxml:
            for fw_entry in dg_entry.find('devices'):
                fw_entry_op = devicegroup_opxml.find(
                    "entry/devices/entry[@name='%s']" % fw_entry.get("name"))
                if fw_entry_op is not None:
                    pandevice.xml_combine(fw_entry, fw_entry_op)

        dg = DeviceGroup()
        dg.parent = self
        devicegroup_instances = dg.refreshall_from_xml(devicegroup_configxml,
                                                       refresh_children=False)

        for dg in devicegroup_instances:
            dg_serials = [
                entry.get("name") for entry in devicegroup_configxml.findall(
                    "entry[@name='%s']/devices/entry" % dg.name)
            ]
            # Find firewall with each serial
            for dg_serial in dg_serials:
                # Skip devices not requested
                if devices and dg_serial not in [str(f) for f in devices]:
                    continue
                all_dg_vsys = [
                    entry.get("name")
                    for entry in devicegroup_configxml.findall(
                        "entry[@name='%s']/devices/entry[@name='%s']/vsys/entry"
                        % (dg.name, dg_serial))
                ]
                # Collect the firewall serial entry to get current status information
                fw_entry = devicegroup_configxml.find(
                    "entry[@name='%s']/devices/entry[@name='%s']" %
                    (dg.name, dg_serial))
                if not all_dg_vsys:
                    # This is a single-context firewall, assume vsys1
                    all_dg_vsys = ["vsys1"]
                for dg_vsys in all_dg_vsys:
                    # Check if this is a requested vsys in devices argument
                    if devices:
                        try:
                            requested_vsys = [f.vsys for f in devices]
                        except AttributeError:
                            # Passed in string serials, no vsys, so get all vsys
                            pass
                        else:
                            if "shared" not in requested_vsys and dg_vsys not in requested_vsys:
                                # A specific vsys was requested, and this isn't it, skip
                                continue
                    fw = next((x for x in firewall_instances
                               if x.serial == dg_serial and x.vsys == dg_vsys),
                              None)
                    if fw is None:
                        # It's possible for device-groups to reference a serial/vsys that doesn't exist
                        # In this case, create the FW instance
                        if not only_connected:
                            fw = self.FIREWALL_CLASS(serial=dg_serial,
                                                     vsys=dg_vsys)
                            dg.add(fw)
                    else:
                        # Move the firewall to the device-group
                        dg.add(fw)
                        firewall_instances.remove(fw)
                        shared_policy_status = fw_entry.findtext(
                            "shared-policy-status")
                        if shared_policy_status is None:
                            shared_policy_status = fw_entry.findtext(
                                "vsys/entry[@name='%s']/shared-policy-status" %
                                dg_vsys)
                        fw.state.set_shared_policy_synced(shared_policy_status)

        if add:
            for dg in devicegroup_instances:
                found_dg = self.find(dg.name, DeviceGroup)
                if found_dg is not None:
                    # Move the firewalls to the existing devicegroup
                    found_dg.removeall(self.FIREWALL_CLASS)
                    found_dg.extend(dg.children)
                else:
                    # Devicegroup doesn't exist, add it
                    self.add(dg)
            # Add firewalls that are not in devicegroups
            self.removeall(self.FIREWALL_CLASS)
            self.extend(firewall_instances)

        return firewall_instances + devicegroup_instances
    def refresh_devices(self, devices=(), only_connected=False, expand_vsys=True, include_device_groups=True, add=False, running_config=False):
        """Refresh device groups and devices using config and operational commands

        Uses operational command in addition to configuration to gather as much information
        as possible about Panorama connected devices. The operational commands used are
        'show devices all/connected' and 'show devicegroups'.

        Information gathered about each device includes:

        - management IP address (can be different from hostname)
        - serial
        - version
        - high availability peer releationships
        - panorama connection status
        - device-group sync status

        Args:
            devices (list): Limit refresh to these serial numbers
            only_connected (bool): Ignore devices that are not 'connected' to Panorama (Default: False)
            expand_vsys (bool): Instantiate a Firewall object for every Vsys (Default: True)
            include_device_groups (bool): Instantiate :class:`pandevice.panorama.DeviceGroup` objects with Firewall
                objects added to them.
            add (bool): Add the new tree of instantiated DeviceGroup and Firewall objects to the Panorama config tree.
                Warning: This removes all current DeviceGroup and Firewall objects from the configuration tree, and all
                their children, so it is typically done before building a configuration tree. (Default: False)
            running_config (bool): Refresh devices from the running configuration (Default: False)

        Returns:
            list: If 'include_device_groups' is True, returns a list containing new DeviceGroup instances which
            contain new Firewall instances. Any Firewall that is not in a device-group is in the list with the
            DeviceGroup instances.
            If 'include_device_groups' is False, returns a list containing new Firewall instances.

        """
        logger.debug(self.id + ": refresh_devices called")
        try:
            # Test if devices is iterable
            test_iterable = iter(devices)
        except TypeError:
            # This probably means a single device was passed in, not an iterable.
            # Convert to an iterable with a single item.
            devices = (devices,)
        # Remove None from list of devices
        devices = [x for x in devices if x is not None]
        # Get the list of managed devices
        if only_connected:
            cmd = "show devices connected"
        else:
            cmd = "show devices all"
        devices_xml = self.op(cmd)
        devices_xml = devices_xml.find("result/devices")

        # Filter to only requested devices
        if devices:
            filtered_devices_xml = ET.Element("devices")
            for device in devices:
                serial = str(device)
                if serial is None:
                    continue
                entry = devices_xml.find("entry[@name='%s']" % serial)
                if entry is None:
                    if only_connected:
                        raise err.PanNotConnectedOnPanorama("Can't find device with serial %s attached and connected"
                                                 " to Panorama at %s" % (serial, self.id))
                    else:
                        raise err.PanNotAttachedOnPanorama("Can't find device with serial %s attached to Panorama at %s" %
                                                 (serial, self.id))
                multi_vsys = yesno(entry.findtext("multi-vsys"))
                try:
                    vsys = device.vsys
                except AttributeError:
                    continue
                # Create entry if needed
                if filtered_devices_xml.find("entry[@name='%s']" % serial) is None:
                    entry_copy = deepcopy(entry)
                    # If looking for specific vsys, erase all vsys in filtered entry
                    if vsys != "shared" and vsys is not None:
                        entry_copy.remove(entry_copy.find("vsys"))
                        ET.SubElement(entry_copy, "vsys")
                    filtered_devices_xml.append(entry_copy)
                # Get specific vsys
                if vsys != "shared" and vsys is not None:
                    vsys_entry = entry.find("vsys/entry[@name='%s']" % vsys)
                    if vsys_entry is None:
                        raise err.PanNotAttachedOnPanorama("Can't find device with serial %s and"
                                                           " vsys %s attached to Panorama at %s" %
                                                           (serial, vsys, self.id))
                    vsys_section = filtered_devices_xml.find("entry[@name='%s']/vsys" % serial)
                    vsys_section.append(vsys_entry)
            devices_xml = filtered_devices_xml

        # Manipulate devices_xml so each vsys is a separate device
        if expand_vsys:
            original_devices_xml = deepcopy(devices_xml)
            devices_xml = ET.Element("devices")
            for entry in original_devices_xml:
                serial = entry.findtext("serial")
                for vsys_entry in entry.findall("vsys/entry"):
                    new_vsys_device = deepcopy(entry)
                    new_vsys_device.set("name", serial)
                    ET.SubElement(new_vsys_device, "vsys_id").text = vsys_entry.get("name")
                    ET.SubElement(new_vsys_device, "vsys_name").text = vsys_entry.findtext("display-name")
                    devices_xml.append(new_vsys_device)

        # Create firewall instances
        tmp_fw = self.FIREWALL_CLASS()
        firewall_instances = tmp_fw.refreshall_from_xml(
            devices_xml, refresh_children=not expand_vsys)

        if not include_device_groups:
            if add:
                self.removeall(self.FIREWALL_CLASS)
                self.extend(firewall_instances)
            return firewall_instances

        # Create device-groups

        # Get the list of device groups from configuration XML
        api_action = self.xapi.show if running_config else self.xapi.get
        devicegroup_configxml = api_action("/config/devices/entry[@name='localhost.localdomain']/device-group")
        devicegroup_configxml = devicegroup_configxml.find("result/device-group")

        # Get the list of device groups from operational commands
        devicegroup_opxml = self.op("show devicegroups")
        devicegroup_opxml = devicegroup_opxml.find("result/devicegroups")

        # Combine the config XML and operational command XML to get a complete picture
        # of the device groups
        if devicegroup_configxml is not None:
            for dg_entry in devicegroup_configxml:
                if dg_entry.find('devices') is None:
                    continue
                for fw_entry in dg_entry.find('devices'):
                    fw_entry_op = devicegroup_opxml.find("entry/devices/entry[@name='%s']" % fw_entry.get("name"))
                    if fw_entry_op is not None:
                        pandevice.xml_combine(fw_entry, fw_entry_op)

        dg = DeviceGroup()
        dg.parent = self
        devicegroup_instances = dg.refreshall_from_xml(
            devicegroup_configxml, refresh_children=False)

        for dg in devicegroup_instances:
            dg_serials = [entry.get("name") for entry in devicegroup_configxml.findall("entry[@name='%s']/devices/entry" % dg.name)]
            # Find firewall with each serial
            for dg_serial in dg_serials:
                # Skip devices not requested
                if devices and dg_serial not in [str(f) for f in devices]:
                    continue
                all_dg_vsys = [entry.get("name") for entry in devicegroup_configxml.findall(
                    "entry[@name='%s']/devices/entry[@name='%s']/vsys/entry" % (dg.name, dg_serial))]
                # Collect the firewall serial entry to get current status information
                fw_entry = devicegroup_configxml.find("entry[@name='%s']/devices/entry[@name='%s']" % (dg.name, dg_serial))
                if not all_dg_vsys:
                    # This is a single-context firewall, assume vsys1
                    all_dg_vsys = ["vsys1"]
                for dg_vsys in all_dg_vsys:
                    # Check if this is a requested vsys in devices argument
                    if devices:
                        try:
                            requested_vsys = [f.vsys for f in devices]
                        except AttributeError:
                            # Passed in string serials, no vsys, so get all vsys
                            pass
                        else:
                            if "shared" not in requested_vsys and None not in requested_vsys and dg_vsys not in requested_vsys:
                                # A specific vsys was requested, and this isn't it, skip
                                continue
                    fw = next((x for x in firewall_instances if x.serial == dg_serial and x.vsys == dg_vsys), None)
                    if fw is None:
                        # It's possible for device-groups to reference a serial/vsys that doesn't exist
                        # In this case, create the FW instance
                        if not only_connected:
                            fw = self.FIREWALL_CLASS(serial=dg_serial, vsys=dg_vsys)
                            dg.add(fw)
                    else:
                        # Move the firewall to the device-group
                        dg.add(fw)
                        firewall_instances.remove(fw)
                        shared_policy_status = fw_entry.findtext("shared-policy-status")
                        if shared_policy_status is None:
                            shared_policy_status = fw_entry.findtext("vsys/entry[@name='%s']/shared-policy-status" % dg_vsys)
                        fw.state.set_shared_policy_synced(shared_policy_status)

        if add:
            for dg in devicegroup_instances:
                found_dg = self.find(dg.name, DeviceGroup)
                if found_dg is not None:
                    # Move the firewalls to the existing devicegroup
                    found_dg.removeall(self.FIREWALL_CLASS)
                    found_dg.extend(dg.children)
                else:
                    # Devicegroup doesn't exist, add it
                    self.add(dg)
            # Add firewalls that are not in devicegroups
            self.removeall(self.FIREWALL_CLASS)
            self.extend(firewall_instances)

        return firewall_instances + devicegroup_instances
Exemple #5
0
    def refresh_devices(self, devices=(), only_connected=False, expand_vsys=True, include_device_groups=True, add=False):
        """Refresh device groups and devices using operational commands"""
        logger.debug(self.hostname + ": refresh_devices called")
        try:
            # Test if devices is iterable
            test_iterable = iter(devices)
        except TypeError:
            # This probably means a single device was passed in, not an iterable.
            # Convert to an iterable with a single item.
            devices = (devices,)
        # Remove None from list of devices
        devices = [x for x in devices if x is not None]
        # Get the list of managed devices
        if only_connected:
            cmd = "show devices connected"
        else:
            cmd = "show devices all"
        devices_xml = self.op(cmd)
        devices_xml = devices_xml.find("result/devices")

        # Filter to only requested devices
        if devices:
            filtered_devices_xml = ET.Element("devices")
            for serial, vsys in [(d.serial, d.vsys) for d in devices]:
                if serial is None:
                    continue
                entry = devices_xml.find("entry[@name='%s']" % serial)
                if entry is None:
                    raise err.PanDeviceError("Can't find device with serial %s attached to Panorama at %s" %
                                             (serial, self.hostname))
                multi_vsys = yesno(entry.findtext("multi-vsys"))
                # Create entry if needed
                if filtered_devices_xml.find("entry[@name='%s']" % serial) is None:
                    entry_copy = deepcopy(entry)
                    # If multivsys firewall with vsys defined, erase all vsys in filtered
                    if multi_vsys and vsys != "shared" and vsys is not None:
                        entry_copy.remove(entry_copy.find("vsys"))
                        ET.SubElement(entry_copy, "vsys")
                    filtered_devices_xml.append(entry_copy)
                # Get specific vsys
                if vsys != "shared" and vsys is not None:
                    vsys_entry = entry.find("vsys/entry[@name='%s']" % vsys)
                    if vsys_entry is None:
                        raise err.PanDeviceError("Can't find device with serial %s and"
                                                 " vsys %s attached to Panorama at %s" %
                                                 (serial, vsys, self.hostname)
                                                 )
                    vsys_section = filtered_devices_xml.find("entry[@name='%s']/vsys" % serial)
                    vsys_section.append(vsys_entry)
            devices_xml = filtered_devices_xml

        # Manipulate devices_xml so each vsys is a separate device
        if expand_vsys:
            original_devices_xml = deepcopy(devices_xml)
            for entry in original_devices_xml:
                multi_vsys = yesno(entry.findtext("multi-vsys"))
                if multi_vsys:
                    serial = entry.findtext("serial")
                    for vsys_entry in entry.findall("vsys/entry"):
                        if vsys_entry.get("name") == "vsys1":
                            continue
                        new_vsys_device = deepcopy(entry)
                        new_vsys_device.set("name", serial)
                        ET.SubElement(new_vsys_device, "vsysid").text = vsys_entry.get("name")
                        ET.SubElement(new_vsys_device, "vsysname").text = vsys_entry.findtext("display-name")
                        devices_xml.append(new_vsys_device)

        # Create firewall instances
        firewall_instances = firewall.Firewall.refresh_all_from_xml(devices_xml, refresh_children=not expand_vsys)

        if not include_device_groups:
            if add:
                self.removeall(firewall.Firewall)
                self.extend(firewall_instances)
            return firewall_instances

        # Create device-groups

        # Get the list of device groups
        devicegroup_xml = self.op("show devicegroups")
        devicegroup_xml = devicegroup_xml.find("result/devicegroups")

        devicegroup_instances = DeviceGroup.refresh_all_from_xml(devicegroup_xml, refresh_children=False)

        for dg in devicegroup_instances:
            dg_serials = [entry.get("name") for entry in devicegroup_xml.findall("entry[@name='%s']/devices/entry" % dg.name)]
            # Find firewall with each serial
            for dg_serial in dg_serials:
                all_dg_vsys = [entry.get("name") for entry in devicegroup_xml.findall("entry[@name='%s']/devices/entry[@name='%s']"
                                                                                  "/vsys/entry" % (dg.name, dg_serial))]
                # Collect the firewall serial entry to get current status information
                fw_entry = devicegroup_xml.find("entry[@name='%s']/devices/entry[@name='%s']" % (dg.name, dg_serial))
                if not all_dg_vsys:
                    # This is a single-context firewall
                    dg_vsys = "vsys1"
                    fw = next((x for x in firewall_instances if x.serial == dg_serial and x.vsys == dg_vsys), None)
                    if fw is None:
                        # It's possible for device-groups to reference a serial/vsys that doesn't exist
                        continue
                    # Move the firewall to the device-group
                    dg.add(fw)
                    firewall_instances.remove(fw)
                    fw.state.connected = yesno(fw_entry.findtext("connected"))
                    fw.state.unsupported_version = yesno(fw_entry.findtext("unsupported-version"))
                    fw.state.set_shared_policy_synced(fw_entry.findtext("shared-policy-status"))
                else:
                    # This is a multi-context firewall
                    for dg_vsys in all_dg_vsys:
                        fw = next((x for x in firewall_instances if x.serial == dg_serial and x.vsys == dg_vsys), None)
                        if fw is None:
                            # It's possible for device-groups to reference a serial/vsys that doesn't exist
                            continue
                        # Move the firewall to the device-group
                        dg.add(fw)
                        firewall_instances.remove(fw)
                        fw.state.connected = yesno(fw_entry.findtext("connected"))
                        fw.state.unsupported_version = yesno(fw_entry.findtext("unsupported-version"))
                        fw.state.set_shared_policy_synced(fw_entry.findtext("shared-policy-status"))

        if add:
            for dg in devicegroup_instances:
                found_dg = self.find(dg.name, DeviceGroup)
                if found_dg is not None:
                    # Move the firewalls to the existing devicegroup
                    found_dg.removeall(firewall.Firewall)
                    found_dg.extend(dg.children)
                else:
                    # Devicegroup doesn't exist, add it
                    self.add(dg)
            # Add firewalls that are not in devicegroups
            self.removeall(firewall.Firewall)
            self.extend(firewall_instances)

        return firewall_instances + devicegroup_instances