def _get_file(self, request, kind, filename): try: data = File(kind, self.get_object().hostname).read() except OSError: raise ParseError("Worker '%s' does not have '%s' file" % (self.get_object().hostname, kind)) response = HttpResponse(data.encode("utf-8"), content_type="application/yaml") response["Content-Disposition"] = "attachment; filename=%s" % filename return response
def show(self, hostname): """ Name ---- `scheduler.workers.show` (`hostname`) Description ----------- Show some details about the given worker. Arguments --------- `hostname`: string Hostname of the worker Return value ------------ This function returns an XML-RPC dictionary with worker details """ try: worker = Worker.objects.get(hostname=hostname) except Worker.DoesNotExist: raise xmlrpc.client.Fault(404, "Worker '%s' was not found." % hostname) data = { "hostname": worker.hostname, "description": worker.description, "state": worker.get_state_display(), "health": worker.get_health_display(), "devices": [d.hostname for d in worker.device_set.all().order_by("hostname")], "last_ping": worker.last_ping, "job_limit": worker.job_limit, "version": worker.version, "default_config": not File("dispatcher", hostname).is_first(), "default_env": not File("env", hostname).is_first(), "default_env_dut": not File("env-dut", hostname).is_first(), } if self.user.is_superuser: data["token"] = worker.token return data
def test_file_device(mocker, tmpdir): mocker.patch( "lava_server.files.File.KINDS", { "device": ([str(tmpdir / "devices")], "{name}.jinja2"), "device-type": ([str(tmpdir / "device-types")], "{name}.jinja2"), }, ) assert File("device", "hello").exists() is False # Create the file File("device", "hello").write("hello world!") assert File("device", "hello").exists() is True assert File("device", "hello").read() == "hello world!" ret = File("device").list("*.jinja2") assert len(ret) == 1 assert ret[0] == "hello.jinja2" ret = File("device").list("*.yaml") assert len(ret) == 0 assert isinstance( File("device").loader(), jinja2.loaders.FileSystemLoader) is True assert File("device").loader().searchpath == [ str(tmpdir / "devices"), str(tmpdir / "device-types"), ]
def test_file_device_type(mocker, tmpdir): mocker.patch( "lava_server.files.File.KINDS", { "device-type": ([str(tmpdir / "0"), str(tmpdir / "1")], "{name}.jinja2") }, ) assert File("device-type", "hello").exists() is False # Test fallback (tmpdir / "1").mkdir() (tmpdir / "1" / "hello.jinja2").write("base") assert File("device-type", "hello").read() == "base" # Create the file File("device-type", "hello").write("new version") assert File("device-type", "hello").exists() is True assert File("device-type", "hello").read() == "new version" ret = File("device-type").list("*.yaml") assert len(ret) == 0 ret = File("device-type").list("*.jinja2") assert len(ret) == 1 assert ret[0] == "hello.jinja2" assert File("device-type").loader().searchpath == [ str(tmpdir / "0"), str(tmpdir / "1"), ]
def set_env(self, hostname, env): """ Name ---- `scheduler.workers.set_env` (`hostname`, `env`) Description ----------- Set the worker environment Arguments --------- `hostname`: string Hostname of the worker `env`: string The worker environment as a yaml file Return value ------------ True if the environment was saved to file, False otherwise. """ # Sanitize hostname as we will use it in a path if len(pathlib.Path(hostname).parts) != 1: raise xmlrpc.client.Fault(400, "Invalid worker name") try: Worker.objects.get(hostname=hostname) except Worker.DoesNotExist: raise xmlrpc.client.Fault(404, "Worker '%s' was not found." % hostname) with contextlib.suppress(OSError): File("env", hostname).write(env) return True return False
def health_check(self, request, **kwargs): if request.method == "GET": if not self.get_object().can_view(request.user): raise Http404( "Device-type '%s' was not found." % self.get_object().name ) try: response = HttpResponse( File("health-check", self.get_object().name).read().encode("utf-8"), content_type="application/yaml", ) response["Content-Disposition"] = ( "attachment; filename=%s_health_check.yaml" % self.get_object().name ) return response except FileNotFoundError: raise ParseError( "Device-type '%s' health check was not found." % self.get_object().name ) except OSError as exc: raise ParseError( "Unable to read health check configuration: %s" % exc.strerror ) elif request.method == "POST": if not request.user.has_perm("lava_scheduler_app.change_device"): raise PermissionDenied( "Insufficient permissions. Please contact system administrator." ) config = request.data.get("config", None) if not config: raise ValidationError( {"config": "Health check configuration is required."} ) try: File("health-check", self.get_object().name).write(config) return Response( {"message": "health check updated"}, status=status.HTTP_204_NO_CONTENT, ) except OSError as exc: raise ParseError( "Unable to write health check configuration: %s" % exc.strerror )
def test_file_env(mocker, tmpdir): mocker.patch( "lava_server.files.File.KINDS", {"env": [str(tmpdir / "{name}/env.yaml"), str(tmpdir / "env.yaml")]}, ) assert File("env", "worker01").exists() is False # Test fallback (tmpdir / "worker01").mkdir() (tmpdir / "worker01" / "env.yaml").write("base") assert File("env", "worker01").read() == "base" # Create the file File("env", "worker01").write("new version") assert File("env", "worker01").exists() is True assert File("env", "worker01").read() == "new version"
def devices(): thread_locals = threading.local() try: return thread_locals.devices except AttributeError: thread_locals.devices = jinja2.Environment( loader=File("device").loader(), autoescape=False, trim_blocks=True) return thread_locals.devices
def _available_device_types(self): """ List avaiable device types by looking at the configuration files """ available_types = [] for device_type in File("device-type").list("*.jinja2"): if not device_type.startswith("base"): available_types.append(device_type[:-7]) available_types.sort() return available_types
def _parse_config(self, hostname): # Will raise OSError if the file does not exist. # Will raise jinja2.TemplateError if the template cannot be parsed. jinja_config = File("device", hostname).read() env = jinja2.Environment( # nosec - YAML, not HTML, no XSS scope. autoescape=False) ast = env.parse(jinja_config) return ast
def config(kind, worker): try: data = File(kind, worker.hostname).read(raising=False) yaml_safe_load(data) return data except yaml.YAMLError: # Raise an OSError because the caller uses yaml.YAMLError for a # specific usage. Allows here to specify the faulty filename. raise OSError("", f"Invalid YAML file for {worker.hostname}: {kind} file")
def test_device_type_templates(self): """ Ensure each template renders valid YAML """ env = jinja2.Environment( # nosec - YAML, not HTML, no XSS scope. loader=File("device-type").loader(), trim_blocks=True, autoescape=False ) for template_name in File("device-type").list("*.jinja2"): try: template = env.get_template(template_name.name) except jinja2.TemplateNotFound as exc: self.fail("%s: %s" % (template_name, exc)) data = None try: data = template.render() yaml_data = yaml_safe_load(data) except yaml.YAMLError as exc: print(data) # for easier debugging - use the online yaml parser self.fail("%s: %s" % (template_name, exc)) self.assertIsInstance(yaml_data, dict)
def template(self, request, **kwargs): if request.method == "GET": if not self.get_object().can_view(request.user): raise Http404("Device-type '%s' was not found." % self.get_object().name) try: filename = f"{self.get_object().name}.jinja2" response = HttpResponse( File("device-type").read(filename).encode("utf-8"), content_type="application/yaml", ) response["Content-Disposition"] = ( "attachment; filename=%s.jinja2" % self.get_object().name) return response except FileNotFoundError: raise ParseError("Device-type '%s' template was not found." % self.get_object().name) except OSError as exc: raise ParseError( "Unable to read device-type configuration: %s" % exc.strerror) elif request.method == "POST": if not request.user.has_perm("lava_scheduler_app.change_device"): raise PermissionDenied( "Insufficient permissions. Please contact system administrator." ) template = request.data.get("template", None) if not template: raise ValidationError( {"template": "Device type template is required."}) try: filename = f"{self.get_object().name}.jinja2" File("device-type").write(filename, template) return Response({"message": "template updated"}, status=status.HTTP_204_NO_CONTENT) except OSError as exc: raise ParseError("Unable to write device-type template: %s" % exc.strerror)
def test_file_errors(mocker, tmpdir): mocker.patch( "lava_server.files.File.KINDS", { "health-check": ([str(tmpdir / "0"), str(tmpdir / "1")], "{name}.jinja2"), "env": [str(tmpdir / "env")], }, ) with pytest.raises(NotImplementedError): File("health-checks") with pytest.raises(NotImplementedError): File("health-check").loader() with pytest.raises(NotImplementedError): File("env").list("*.yaml") with pytest.raises(FileNotFoundError): File("health-check", "docker").read() assert File("health-check", "docker").read(raising=False) == ""
def test_bbb_valid_template(self): name = "beaglebone-black" dt = DeviceType(name=name) dt.save() dt.refresh_from_db() device = Device(device_type=dt, hostname="bbb-01", health=Device.HEALTH_GOOD) device.save() device.refresh_from_db() self.assertIsNotNone([d for d in Device.objects.filter(device_type=dt)]) self.assertTrue(File("device-type").exists(f"{name}.jinja2")) self.assertIsNotNone([device in Device.objects.filter(device_type=dt)]) self.assertIsNotNone(device.load_configuration()) self.assertTrue(bool(load_devicetype_template(device.device_type.name))) self.assertFalse(invalid_template(device.device_type))
def show(self, name): """ Name ---- `scheduler.device_types.show` (`name`) Description ----------- Show some details about the given device type. Arguments --------- `name`: string Name of the device-type Return value ------------ This function returns an XML-RPC dictionary with device-type details """ try: dt = DeviceType.objects.get(name=name) except DeviceType.DoesNotExist: raise xmlrpc.client.Fault(404, "Device-type '%s' was not found." % name) if not dt.can_view(self.user): raise xmlrpc.client.Fault(404, "Device-type '%s' was not found." % name) aliases = [str(alias.name) for alias in dt.aliases.all()] devices = [ str(d.hostname) for d in dt.device_set.all() if d.can_view(self.user) ] dt_dict = { "name": dt.name, "description": dt.description, "display": dt.display, "health_disabled": dt.disable_health_check, "health_denominator": dt.get_health_denominator_display(), "health_frequency": dt.health_frequency, "aliases": aliases, "devices": devices, "default_template": not File("device-type", name).is_first(), } return dt_dict
def get_template(self, name): """ Name ---- `scheduler.device_types.get_template` (`name`) Description ----------- Return the device-type configuration for the requested device-type or filename. Note: not all device-types have a health check filename that matches the device-type name in the database. Arguments --------- `name`: string Name of the device-type The .jinja2 suffix will be added if not specified. Return value ------------ The device-type configuration """ with contextlib.suppress(DeviceType.DoesNotExist): dt = DeviceType.objects.get(name=name) if not dt.can_view(self.user): raise xmlrpc.client.Fault( 404, "Device-type '%s' was not found." % name) # Filename should not be a path or starting with a dot if os.path.basename(name) != name or name[0] == ".": raise xmlrpc.client.Fault(400, "Invalid device-type '%s'" % name) try: return xmlrpc.client.Binary( File("device-type", name).read().encode("utf-8")) except FileNotFoundError: raise xmlrpc.client.Fault(404, "Device-type '%s' was not found." % name) except OSError as exc: raise xmlrpc.client.Fault( 400, "Unable to read device-type configuration: %s" % exc.strerror)
def get_config(self, hostname): """ Name ---- `scheduler.workers.get_config` (`hostname`) Description ----------- Return the worker configuration The server will first try /etc/lava-server/dispatcher.d/<hostname>/dispatcher.yaml and fallback to /etc/lava-server/dispatcher.d/<hostname>.yaml Arguments --------- `hostname`: string Hostname of the worker Return value ------------ This function returns the worker configuration """ # Sanitize hostname as we will use it in a path if len(pathlib.Path(hostname).parts) != 1: raise xmlrpc.client.Fault(400, "Invalid worker name") # Find the worker in the database try: Worker.objects.get(hostname=hostname) except Worker.DoesNotExist: raise xmlrpc.client.Fault(404, "Worker '%s' was not found." % hostname) try: return xmlrpc.client.Binary( File("dispatcher", hostname).read().encode("utf-8") ) except FileNotFoundError: raise xmlrpc.client.Fault( 404, "Worker '%s' does not have a dispatcher configuration" % hostname ) except OSError as exc: raise xmlrpc.client.Fault( 400, "Unable to read dispatcher configuration: %s" % exc.strerror )
def test_juno_vexpress_valid_template(self): name = "juno-r2" dt = DeviceType(name=name) dt.save() dt.refresh_from_db() device = Device( device_type=dt, hostname="juno-r2-01", health=Device.HEALTH_GOOD ) device.save() device.refresh_from_db() self.assertIsNotNone([d for d in Device.objects.filter(device_type=dt)]) self.assertFalse(File("device-type").exists("juno-r2.jinja2")) self.assertEqual("juno-r2-01", device.hostname) self.assertIsNotNone(device.load_configuration()) self.assertEqual( [device], [device for device in Device.objects.filter(device_type=dt)] ) self.assertEqual("juno", device.get_extends()) self.assertFalse(bool(load_devicetype_template(device.device_type.name))) self.assertFalse(invalid_template(device.device_type))
def set_template(self, name, config): """ Name ---- `scheduler.device_types.set_template` (`name`, `config`) Description ----------- [superuser only] Set the device-type configuration for the requested device-type or filename. Note: not all device-types have a health check filename that matches the device-type name in the database. Arguments --------- `name`: string name of the device-type `config`: string The device-type configuration as a jinja2 template The .jinja2 suffix will be added if not specified. Return value ------------ None """ # Filename should not be a path or starting with a dot if os.path.basename(name) != name or name[0] == ".": raise xmlrpc.client.Fault(400, "Invalid device-type '%s'" % name) try: name = name if name.endswith(".jinja2") else name + ".jinja2" File("device-type").write(name, config) except OSError as exc: raise xmlrpc.client.Fault( 400, "Unable to write device-type configuration: %s" % exc.strerror )
def validate(self, request, **kwargs): """ Takes a string of a device dictionary to validate if it can be rendered and loaded correctly. """ devicedict = request.data if not devicedict: raise ValidationError({"device": "Device dictionary is required."}) try: template = jinja2.Environment( loader=File("device").loader(), autoescape=False, trim_blocks=True).from_string(devicedict) yaml_safe_load(template.render()) return Response({"message": "Device dictionary valid."}, status=status.HTTP_200_OK) except Exception as exc: return Response( {"message": "Device dictionary invalid: %s" % str(exc)}, status=status.HTTP_200_OK, )
def set_health_check(self, name, config): """ Name ---- `scheduler.device_types.set_health_check` (`name`, `config`) Description ----------- [superuser only] Set the health-check definition for the requested device-type or filename. Note: not all device-types have a health check filename that matches the device-type name in the database. Arguments --------- `name`: string name of the device-type `config`: string The health-check as a yaml file The .yaml suffix will be added if not specified. Return value ------------ None """ # Filename should not be a path or starting with a dot if os.path.basename(name) != name or name[0] == ".": raise xmlrpc.client.Fault(400, "Invalid device-type '%s'" % name) try: File("health-check", name).write(config) except OSError as exc: raise xmlrpc.client.Fault( 400, "Unable to write health-check: %s" % exc.strerror)
# # LAVA is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with LAVA. If not, see <http://www.gnu.org/licenses/>. import jinja2 import threading from lava_server.files import File thread_locals = threading.local() thread_locals.devices = jinja2.Environment(loader=File("device").loader(), autoescape=False, trim_blocks=True) thread_locals.device_types = jinja2.Environment( loader=File("device-type").loader(), autoescape=False, trim_blocks=True) def devices(): return thread_locals.devices def device_types(): return thread_locals.device_types
def handle_copy(self, options): original = options["original"] target = options["target"] worker_name = options["worker"] public = options["public"] online = options["online"] tags = options["copytags"] try: from_device = Device.objects.get(hostname=original) except Device.DoesNotExist: raise CommandError("Original device '%s' does NOT exist!" % original) with contextlib.suppress(Device.DoesNotExist): Device.objects.get(hostname=target) raise CommandError("Target device '%s' already exists" % target) origin = from_device.load_configuration() if not origin: raise CommandError("Device dictionary does not exist for %s" % original) if online: if not File("device").exists(f"{target}.jinja2"): raise CommandError( "Refusing to copy %s to new device %s with health 'Good' -" " no device dictionary exists for target device, yet. " "Use --offline or copy %s.jinja2 and try again." % (original, target, target)) try: worker = Worker.objects.get(hostname=worker_name) except Worker.DoesNotExist: raise CommandError("Unable to find worker '%s'" % worker_name) health = Device.HEALTH_GOOD if online else Device.HEALTH_MAINTENANCE description = from_device.description device_type = from_device.device_type from_tags = None if tags: from_tags = from_device.tags.all() physical_owner = None physical_group = None owner = None group = None if from_device.physical_owner: physical_owner = from_device.physical_owner elif from_device.physical_group: physical_group = from_device.physical_group if from_device.owner: owner = from_device.owner elif from_device.group: group = from_device.group with transaction.atomic(): device = Device.objects.create( hostname=target, device_type=device_type, description=description, state=Device.STATE_IDLE, health=health, worker_host=worker, ) if from_tags is not None: for tag in from_tags: device.tags.add(tag) if physical_owner: self._assign(physical_owner, device, user=True, physical=True) elif physical_group: self._assign(physical_group, device, group=True, physical=True) if owner: self._assign(owner, device, user=True, owner=True) elif group: self._assign(group, device, group=True, owner=True) device.save() destiny = device.load_configuration() if not destiny: print("Reminder: device dictionary does not yet exist for %s" % target)
def handle(self, *_, **options): dicts = File("device").list("*.jinja2") synced_devices = [] self.stdout.write("Scanning devices:") for name in dicts: hostname = name.rsplit(".", 1)[0] # Get value of 'sync_to_lava' variable from template. sync_dict = self._get_sync_to_lava(hostname) if sync_dict is None: self.stdout.write(f"* {hostname} [SKIP]") self.stdout.write(f" -> missing '{self.SYNC_KEY}'") continue # Convert it to dictionary. sync_dict = self._parse_sync_dict(sync_dict) try: template = environment.devices().get_template(name) device_template = yaml_safe_load(template.render()) except jinja2.TemplateError as exc: self.stdout.write(f"* {hostname} [SKIP]") self.stdout.write(f" -> invalid jinja2 template") self.stdout.write(f" -> {exc}") continue except yaml.YAMLError as exc: self.stdout.write(f"* {hostname} [SKIP]") self.stdout.write(f" -> invalid yaml") self.stdout.write(f" -> {exc}") continue # Check if this device is already manually created in db. with contextlib.suppress(Device.DoesNotExist): device = Device.objects.get(hostname=hostname) if not device.is_synced: self.stdout.write(f"* {hostname} [SKIP]") self.stdout.write(f" -> created manually") continue # Check keys if "device_type" not in sync_dict: self.stdout.write(f"* {hostname} [SKIP]") self.stdout.write(f" -> 'device_type' is mandatory") continue # Add to managed devices list. self.stdout.write(f"* {hostname}") synced_devices.append(hostname) # Create device type. If not found, report an error and skip. device_type, created = DeviceType.objects.get_or_create( name=sync_dict["device_type"]) if created: self.stdout.write( f" -> create device type: {device_type.name}") worker = None if "worker" in sync_dict: worker, created = Worker.objects.get_or_create( hostname=sync_dict["worker"]) if created: self.stdout.write( f" -> create worker: {sync_dict['worker']}") # Create/update device. defaults = { "device_type": device_type, "description": "Created automatically by LAVA.", "worker_host": worker, "is_synced": True, } device, created = Device.objects.update_or_create( defaults, hostname=hostname) if created: Device.objects.filter(hostname=hostname).update( health=Device.HEALTH_UNKNOWN) # Create aliases and tags. for alias_name in sync_dict.get("aliases", []): Alias.objects.get_or_create(name=alias_name, device_type=device_type) self.stdout.write(f" -> alias: {alias_name}") for tag_name in sync_dict.get("tags", []): tag, _ = Tag.objects.get_or_create(name=tag_name) device.tags.add(tag) self.stdout.write(f" -> tag: {tag_name}") # devices which have is_synced true if there's no device dict for them. Device.objects.filter(is_synced=True).exclude( hostname__in=synced_devices).update(health=Device.HEALTH_RETIRED) # Device types which have all the devices synced and all of them retired # should become invisible. dts = (DeviceType.objects.annotate(not_synced_retired_count=Count( Case( When( Q(device__is_synced=False) | ~Q(device__health=Device.HEALTH_RETIRED), then=1, ), output_field=IntegerField(), ))).filter(not_synced_retired_count=0).update(display=False))
def handle(self, *_, **options): dicts = File("device").list("*.jinja2") synced_devices = [] self.stdout.write("Scanning devices:") for name in dicts: hostname = name.rsplit(".", 1)[0] # Get value of 'sync_to_lava' variable from template. sync_dict, exc = self._get_sync_to_lava(hostname) if exc: self.stdout.write(f"* {hostname} [SKIP]") self.stdout.write(f" -> invalid jinja2 template") self.stdout.write(f" -> {exc}") continue if sync_dict is None: self.stdout.write(f"* {hostname} [SKIP]") self.stdout.write(f" -> missing '{self.SYNC_KEY}'") continue # Convert it to dictionary. sync_dict = self._parse_sync_dict(sync_dict) try: template = environment.devices().get_template(name) device_template = yaml_safe_load(template.render()) except jinja2.TemplateError as exc: self.stdout.write(f"* {hostname} [SKIP]") self.stdout.write(f" -> invalid jinja2 template") self.stdout.write(f" -> {exc}") continue except yaml.YAMLError as exc: self.stdout.write(f"* {hostname} [SKIP]") self.stdout.write(f" -> invalid yaml") self.stdout.write(f" -> {exc}") continue # Check if this device is already manually created in db. with contextlib.suppress(Device.DoesNotExist): device = Device.objects.get(hostname=hostname) if not device.is_synced: self.stdout.write(f"* {hostname} [SKIP]") self.stdout.write(f" -> created manually") continue # Check keys if "device_type" not in sync_dict: self.stdout.write(f"* {hostname} [SKIP]") self.stdout.write(f" -> 'device_type' is mandatory") continue # Add to managed devices list. self.stdout.write(f"* {hostname}") synced_devices.append(hostname) # Create device type. If not found, report an error and skip. device_type, created = DeviceType.objects.get_or_create( name=sync_dict["device_type"]) if created: self.stdout.write( f" -> create device type: {device_type.name}") worker = None if "worker" in sync_dict: worker, created = Worker.objects.get_or_create( hostname=sync_dict["worker"]) if created: self.stdout.write( f" -> create worker: {sync_dict['worker']}") # Create/update device. defaults = { "device_type": device_type, "description": "Created automatically by LAVA.", "worker_host": worker, "is_synced": True, } device, created = Device.objects.update_or_create( defaults, hostname=hostname) if created: Device.objects.filter(hostname=hostname).update( health=Device.HEALTH_UNKNOWN) # Create aliases. for alias_name in sync_dict.get("aliases", []): Alias.objects.get_or_create(name=alias_name, device_type=device_type) self.stdout.write(f" -> alias: {alias_name}") # Remove all tag relations first. device.tags.clear() # Create tags. for tag_name in sync_dict.get("tags", []): tag, _ = Tag.objects.get_or_create(name=tag_name) device.tags.add(tag) self.stdout.write(f" -> tag: {tag_name}") # Link physical owner specified_owner = sync_dict.get("physical_owner", "") try: physical_owner = User.objects.get(username=specified_owner) device.physical_owner = physical_owner self.stdout.write(f" -> user: {specified_owner}") except User.DoesNotExist: device.physical_owner = None if specified_owner: self.stdout.write( f" -> user '{specified_owner}' does not exist") finally: device.save() # Link physical group specified_group = sync_dict.get("physical_group", "") try: physical_group = Group.objects.get(name=specified_group) device.physical_group = physical_group self.stdout.write(f" -> group: {specified_group}") except Group.DoesNotExist: device.physical_group = None if specified_group: self.stdout.write( f" -> group '{specified_group}' does not exist") finally: device.save() # Assign permission specified_permissions = sync_dict.get("group_device_permissions", []) for permission in specified_permissions: perm = permission[0] group = permission[1] try: permission_group = Group.objects.get(name=group) try: GroupDevicePermission.objects.assign_perm( perm, permission_group, device) self.stdout.write( f" -> add group permission: ({perm}, {group})") except PermissionNameError: self.stdout.write( f" -> permission '{perm}' does not exist") except Group.DoesNotExist: self.stdout.write(f" -> group '{group}' does not exist") # Delete unused permission kwargs = {"device": device} obj_perm = GroupDevicePermission.objects.filter(**kwargs) for perm in obj_perm: if [ perm.permission.codename, perm.group.name, ] not in specified_permissions: GroupDevicePermission.objects.remove_perm( perm.permission.codename, perm.group, perm.device) self.stdout.write( f" -> delete group permission: ({perm.permission.codename}, {perm.group.name})" ) # devices which have is_synced true if there's no device dict for them. Device.objects.filter(is_synced=True).exclude( hostname__in=synced_devices).update(health=Device.HEALTH_RETIRED) # Device types which have all the devices synced and all of them retired # should become invisible. synced_retired_queryset = DeviceType.objects.annotate( not_synced_retired_count=Count( Case( When( Q(device__is_synced=False) | ~Q(device__health=Device.HEALTH_RETIRED), then=1, ), output_field=IntegerField(), ))) synced_retired_queryset.filter(not_synced_retired_count=0).update( display=False) # Device types which have all the devices synced and some of them not # retired should become visible. synced_not_retired_queryset = DeviceType.objects.annotate( not_synced=Count( Case( When(Q(device__is_synced=False), then=1), output_field=IntegerField(), )), not_retired=Count( Case( When(~Q(device__health=Device.HEALTH_RETIRED), then=1), output_field=IntegerField(), )), ) synced_not_retired_queryset.filter(not_synced=0).filter( not_retired__gt=0).update(display=True)
def device_types(): return jinja2.Environment( loader=File("device-type").loader(), autoescape=False, trim_blocks=True )