def test__can_watch_any_signal(self): django_signal = pick_django_signal() manager = SignalsManager() manager.watch( django_signal, sentinel.callback, sender=sentinel.sender, weak=sentinel.weak, dispatch_uid=sentinel.dispatch_uid, ) self.assertThat(manager._signals, HasLength(1)) [signal] = manager._signals self.assertThat( signal.connect, MatchesPartialCall( django_signal.connect, sentinel.callback, sender=sentinel.sender, weak=sentinel.weak, dispatch_uid=sentinel.dispatch_uid, ), ) self.assertThat( signal.disconnect, MatchesPartialCall( django_signal.disconnect, sentinel.callback, sender=sentinel.sender, dispatch_uid=sentinel.dispatch_uid, ), )
log = LegacyLogger() signals = SignalsManager() def post_save__update_version_notifications( sender, instance, created, **kwargs ): update_version_notifications() def post_delete__update_version_notifications(sender, instance, **kwargs): update_version_notifications() signals.watch( post_save, post_save__update_version_notifications, sender=ControllerInfo ) signals.watch( post_delete, post_delete__update_version_notifications, sender=ControllerInfo, ) # For some reason (maube due to the cascading delete behavior), we need to # place signals not only on ControllerInfo, but also RegionController and # RackController. signals.watch( post_delete, post_delete__update_version_notifications, sender=RegionController, )
parent.vlan = instance.vlan parent.save() log.msg( "%s: VLAN updated to match %s (vlan=%s)." % ( parent.get_log_string(), instance.get_log_string(), parent.vlan_id, ) ) finally: visiting.discard(parent.id) for klass in INTERFACE_CLASSES: signals.watch(post_save, update_interface_parents, sender=klass) def interface_vlan_update(instance, old_values, **kwargs): """When an interfaces VLAN is changed we need to do the following. * If its a controller move all assigned subnets to the new VLAN. This is done because the subnets defined are discovered on the controller and an administrator cannot change them. * If its a machine or device then we need to remove all links if the VLAN is different. """ new_vlan_id = instance.vlan_id [old_vlan_id] = old_values if new_vlan_id == old_vlan_id: # Nothing changed do nothing.
__all__ = ["signals"] from django.db.models.signals import post_delete from maasserver.models.largefile import ( delete_large_object_content_later, LargeFile, ) from maasserver.utils.orm import post_commit_do from maasserver.utils.signals import SignalsManager signals = SignalsManager() def delete_large_object(sender, instance, **kwargs): """Delete the large object when the `LargeFile` is deleted. This is done using the `post_delete` signal instead of overriding delete on `LargeFile`, so it works correctly for both the model and `QuerySet`. """ if instance.content is not None: post_commit_do(delete_large_object_content_later, instance.content) signals.watch(post_delete, delete_large_object, LargeFile) # Enable all signals by default. signals.enable()
"""Store the instance's bmc_set for use in post_delete. It is coerced to a set to force it to be evaluated here in the pre_delete handler. Otherwise, the call will be deferred until evaluated in post_delete, where the results will be invalid because the instance will be gone. This information is necessary so any BMC's using this deleted IP address can be called on in post_delete to make their own StaticIPAddresses. """ instance.__previous_bmcs = set(instance.bmc_set.all()) instance.__previous_dnsresources = set(instance.dnsresource_set.all()) signals.watch(pre_delete, pre_delete_record_relations_on_delete, sender=StaticIPAddress) def post_delete_remake_sip_for_bmc(sender, instance, **kwargs): """Now that the StaticIPAddress instance is gone, ask each BMC that was using it to make a new one. When a StaticIPAddress is deleted, any BMC models sharing it will automatically set their ip_address links to None. They are then recreated here in post_delete. """ for bmc in instance.__previous_bmcs: # This BMC model instance was created in pre_delete and hasn't been # updated to reflect the just executed deletion. Set the ip_address to # None to replicate this. We can avoid the DB hit as we always want a
from maasserver.enum import BMC_TYPE from maasserver.models import BMC, Pod, PodHints from maasserver.utils.signals import SignalsManager BMC_CLASSES = [BMC, Pod] signals = SignalsManager() def pre_delete_bmc_clean_orphaned_ip(sender, instance, **kwargs): """Stash the soon-to-be-deleted BMC's ip_address for use in post_delete.""" instance.__previous_ip_address = instance.ip_address for klass in BMC_CLASSES: signals.watch(pre_delete, pre_delete_bmc_clean_orphaned_ip, sender=klass) def post_delete_bmc_clean_orphaned_ip(sender, instance, **kwargs): """Removes the just-deleted BMC's ip_address if nobody else is using it. The potentially orphaned ip_address was stashed in the instance by the pre-delete signal handler. """ if instance.__previous_ip_address is None: return if instance.__previous_ip_address.get_node() is not None: return if instance.__previous_ip_address.bmc_set.count() > 0: return # Delete the orphaned interface.
# Copyright 2016 Canonical Ltd. This software is licensed under the # GNU Affero General Public License version 3 (see the file LICENSE). """Respond to DHCPSnippet changes.""" __all__ = ["signals"] from django.db.models.signals import post_delete from maasserver.models import DHCPSnippet from maasserver.utils.signals import SignalsManager signals = SignalsManager() def post_delete_dhcp_snippet_clean_values(sender, instance, **kwargs): """Removes the just-deleted DHCPSnippet's set of values.""" for value in instance.value.previous_versions(): value.delete() signals.watch(post_delete, post_delete_dhcp_snippet_clean_values, sender=DHCPSnippet) # Enable all signals by default. signals.enable()
"""Delete KeySource when no more keys are present.""" __all__ = ["signals"] from django.db.models.signals import post_delete from maasserver.models import KeySource, SSHKey from maasserver.utils.signals import SignalsManager signals = SignalsManager() def post_delete_keysource_when_no_more_keys(sender, instance, **kwargs): """Delete Keysource when no more keys.""" keysource = None try: keysource = instance.keysource except KeySource.DoesNotExist: pass # Nothing to do. else: if keysource is not None: if not keysource.sshkey_set.exists(): keysource.delete() signals.watch(post_delete, post_delete_keysource_when_no_more_keys, sender=SSHKey) # Enable all signals by default. signals.enable()
# GNU Affero General Public License version 3 (see the file LICENSE). """Respond to user changes.""" __all__ = [ "signals", ] from django.contrib.auth.models import User from django.db.models.signals import pre_delete from maasserver.utils.signals import SignalsManager signals = SignalsManager() USER_CLASSES = [ User, ] def pre_delete_set_event_username(sender, instance, **kwargs): """Set username for events that reference user being deleted.""" for event in instance.event_set.all(): event.username = instance.username event.save() for klass in USER_CLASSES: signals.watch(pre_delete, pre_delete_set_event_username, sender=klass) # Enable all signals by default. signals.enable()
# Copyright 2020 Canonical Ltd. This software is licensed under the # GNU Affero General Public License version 3 (see the file LICENSE). """Respond to PodHints changes.""" from django.db.models.signals import m2m_changed from maasserver.models import PodHints from maasserver.utils.signals import SignalsManager signals = SignalsManager() def pod_nodes_changed(sender, instance, action, reverse, model, pk_set, **kwargs): if action == "post_remove": # Recalculate resources based on the remaining Nodes. instance.pod.sync_hints_from_nodes() signals.watch(m2m_changed, pod_nodes_changed, sender=PodHints.nodes.through) # Enable all signals by default signals.enable()
from maasserver.models.regionrackrpcconnection import RegionRackRPCConnection from maasserver.models.service import Service from maasserver.utils.signals import SignalsManager signals = SignalsManager() def update_rackd_status(sender, instance, **kwargs): """Update status of the rackd service for the rack controller the RPC connection was added or removed. """ Service.objects.create_services_for(instance.rack_controller) instance.rack_controller.update_rackd_status() signals.watch(post_save, update_rackd_status, sender=RegionRackRPCConnection) signals.watch(post_delete, update_rackd_status, sender=RegionRackRPCConnection) def update_all_rackd_status(sender, instance, **kwargs): """Update status of all rackd services when a region controller process is added or removed. """ for rack_controller in RackController.objects.all(): Service.objects.create_services_for(rack_controller) rack_controller.update_rackd_status() signals.watch(post_save, update_all_rackd_status,
"""Respond to partition changes.""" from django.db.models.signals import post_delete from maasserver.models.partition import Partition from maasserver.models.partitiontable import PartitionTable from maasserver.utils.signals import SignalsManager signals = SignalsManager() def delete_partition_table(sender, instance, **kwargs): """Delete the partition table if this is the last partition on the partition table.""" try: partition_table = instance.partition_table except PartitionTable.DoesNotExist: pass # Nothing to do. else: if partition_table.partitions.count() == 0: partition_table.delete() signals.watch(post_delete, delete_partition_table, Partition) # Enable all signals by default. signals.enable()
try: if instance.subnet is None: return except ObjectDoesNotExist: return instance.subnet.update_allocation_notification() def post_delete_check_range_utilization(sender, instance, **kwargs): # Be careful when checking for the subnet. In rare cases, such as a # cascading delete, Django can sometimes pass stale model objects into # signal handlers, which will raise unexpected DoesNotExist exceptions, # and/or otherwise invalidate foreign key fields. # See bug #1702527 for more details. try: if instance.subnet is None: return except ObjectDoesNotExist: return instance.subnet.update_allocation_notification() signals.watch( post_save, post_save_check_range_utilization, sender=IPRange) signals.watch( post_delete, post_delete_check_range_utilization, sender=IPRange) # Enable all signals by default. signals.enable()
# fix the size of the VirtualBlockDevice if the size of this block # device has changed. group.save() if isinstance(block_device, VirtualBlockDevice): # When not LVM the name of the block devices should stay in sync # with the name of the filesystem group. filesystem_group = block_device.filesystem_group if (filesystem_group.group_type != FILESYSTEM_GROUP_TYPE.LVM_VG and filesystem_group.name != block_device.name): filesystem_group.name = block_device.name filesystem_group.save() for sender in senders: signals.watch(post_save, update_filesystem_group, sender) def delete_filesystem_group(sender, instance, **kwargs): """Delete the attached `FilesystemGroup` when it is not LVM.""" block_device = instance.actual_instance if isinstance(block_device, VirtualBlockDevice): try: filesystem_group = block_device.filesystem_group except FilesystemGroup.DoesNotExist: # Possible that it was deleted the same time this # virtual block device was deleted. return not_volume_group = (filesystem_group.group_type != FILESYSTEM_GROUP_TYPE.LVM_VG) if filesystem_group.id is not None and not_volume_group:
from django.db.models.signals import post_delete from maasserver.models.bootresourcefile import BootResourceFile from maasserver.models.largefile import LargeFile from maasserver.utils.signals import SignalsManager signals = SignalsManager() def delete_large_file(sender, instance, **kwargs): """Call delete on the LargeFile, now that the relation has been removed. If this was the only resource file referencing this LargeFile then it will be delete. This is done using the `post_delete` signal because only then has the relation been removed. """ try: largefile = instance.largefile except LargeFile.DoesNotExist: pass # Nothing to do. else: if largefile is not None: largefile.delete() signals.watch(post_delete, delete_large_file, BootResourceFile) # Enable all signals by default. signals.enable()
signals = SignalsManager() def save_boot_source_cache(sender, instance, *args, **kwargs): """On first run the ImportResourceService sets the default BootSource then caches the stream's contents as normal. Setting the default BootSource triggers this signal. This prevents updating the cache twice. """ # Don't run if the first row and newly created. if instance.id != 1 and BootSource.objects.count() != 0: update_boot_source_cache(sender, instance, *args, **kwargs) def update_boot_source_cache(sender, instance, *args, **kwargs): """Update the `BootSourceCache` using the updated source. This only begins after a successful commit to the database, and is then run in a thread. Nothing waits for its completion. """ post_commit_do(reactor.callLater, 0, cache_boot_sources) signals.watch(post_save, save_boot_source_cache, BootSource) signals.watch(post_delete, update_boot_source_cache, BootSource) signals.watch_config(update_boot_source_cache, "enable_http_proxy") signals.watch_config(update_boot_source_cache, "http_proxy") # Enable all signals by default. signals.enable()
"""Updates the `StaticIPAddress`'s to ensure that they are linked to the correct subnet.""" # Remove the IP addresses that no longer fall with in the CIDR. remove_ips = StaticIPAddress.objects.filter( alloc_type=IPADDRESS_TYPE.USER_RESERVED, subnet_id=subnet.id) remove_ips = remove_ips.extra(where=["NOT(ip << %s)"], params=[subnet.cidr]) remove_ips.update(subnet=None) # Add the IP addresses that now fall into CIDR. add_ips = StaticIPAddress.objects.filter(subnet__isnull=True) add_ips = add_ips.extra(where=["ip << %s"], params=[subnet.cidr]) add_ips.update(subnet_id=subnet.id) def post_created(sender, instance, created, **kwargs): if created: update_referenced_ip_addresses(instance) def updated_cidr(instance, old_values, **kwargs): update_referenced_ip_addresses(instance) signals.watch(post_save, post_created, sender=Subnet) signals.watch_fields(updated_cidr, Subnet, ["cidr"], delete=False) # Enable all signals by default. signals.enable()
Controller, RackController, RegionController, ] signals = SignalsManager() def pre_delete_update_events(sender, instance, **kwargs): """Update node hostname and id for events related to the node.""" instance.event_set.all().update(node_hostname=instance.hostname, node_id=None) for klass in NODE_CLASSES: signals.watch(pre_delete, pre_delete_update_events, sender=klass) def post_init_store_previous_status(sender, instance, **kwargs): """Store the pre_save status of the instance.""" instance.__previous_status = instance.status for klass in NODE_CLASSES: signals.watch(post_init, post_init_store_previous_status, sender=klass) def pre_save_update_status(sender, instance, **kwargs): """Update node previous_status when node status changes.""" if (instance.__previous_status != instance.status and instance.__previous_status