def isolate(request, instance): if request.user.is_superuser or request.user.has_perm("ganeti.view_instances"): if instance: try: instance = Instance.objects.get(name=instance) except ObjectDoesNotExist: raise Http404 instance_isolate = instance.isolate if request.method == "POST": form = isolateForm(request.POST) if form.is_valid(): isolate = form.cleaned_data["isolate"] if isolate: auditlog = auditlog_entry(request, "Isolate", instance, instance.cluster.slug) jobid = instance.cluster.tag_instance(instance.name, ["%s:isolate" % settings.GANETI_TAG_PREFIX]) auditlog.update(job_id=jobid) instance.cluster.migrate_instance(instance.name) if not isolate: auditlog = auditlog_entry(request, "De-Isolate", instance, instance.cluster.slug) jobid = instance.cluster.untag_instance(instance.name, ["%s:isolate" % settings.GANETI_TAG_PREFIX]) auditlog.update(job_id=jobid) instance.cluster.migrate_instance(instance.name) res = {"result": "success"} return HttpResponse(json.dumps(res), mimetype="application/json") else: return render(request, "tagging/isolate.html", {"form": form, "instance": instance}) elif request.method == "GET": form = isolateForm(initial={"isolate": instance_isolate}) return render(request, "tagging/isolate.html", {"form": form, "instance": instance}) else: return HttpResponseRedirect(reverse("user-instances"))
def lock(request, instance): if request.user.is_superuser or request.user.has_perm("ganeti.view_instances"): if instance: try: instance = Instance.objects.get(name=instance) except ObjectDoesNotExist: raise Http404() instance_adminlock = instance.adminlock if request.method == "POST": form = lockForm(request.POST) if form.is_valid(): adminlock = form.cleaned_data["lock"] if adminlock: auditlog = auditlog_entry(request, "Lock Instance", instance, instance.cluster.slug) # set the cache for a week cache.set("instances:%s" % (instance.name), True, 60 * 60 * 24 * 7) jobid = instance.cluster.tag_instance(instance.name, ["%s:adminlock" % settings.GANETI_TAG_PREFIX]) auditlog.update(job_id=jobid) if adminlock is False: cache.delete("instances:%s" % (instance.name)) auditlog = auditlog_entry(request, "Unlock Instance", instance, instance.cluster.slug) jobid = instance.cluster.untag_instance( instance.name, ["%s:adminlock" % settings.GANETI_TAG_PREFIX] ) auditlog.update(job_id=jobid) res = {"result": "success"} return HttpResponse(json.dumps(res), mimetype="application/json") else: return render(request, "tagging/lock.html", {"form": form, "instance": instance}) elif request.method == "GET": form = lockForm(initial={"lock": instance_adminlock}) return render(request, "tagging/lock.html", {"form": form, "instance": instance}) else: return HttpResponseRedirect(reverse("user-instances"))
def test_auditlog_view(self): # dont allow access in unauthorized users res = self.client.get(reverse('auditlog')) self.assertEqual(res.status_code, 302) # users have access to their logs self.client.login(username='******', password='******') res = self.client.get(reverse('auditlog')) self.assertEqual(res.status_code, 200) res = self.client.get(reverse('auditlog_json')) self.assertEqual(res.status_code, 200) # admin users have access self.client.login(username='******', password='******') res = self.client.get(reverse('auditlog')) self.assertEqual(res.status_code, 200) res = self.client.get(reverse('auditlog_json')) self.assertEqual(res.status_code, 200) # the response should be empty response = json.loads(res.content) self.assertEqual(len(response['aaData']), 0) # lets create an audit entry # shudtdown instance 'test' # user = audittestadmin request = self.factory.get(reverse('auditlog_json')) request.user = self.superuser entry = auditlog_entry(request, "Shutdown", 'test', 'test') # get again the auditlog res = self.client.get(reverse('auditlog_json')) self.assertEqual(res.status_code, 200) response = json.loads(res.content) self.assertEqual(response['aaData'][0]['user'], entry.requester.username) # the response should not be empty this time response = json.loads(res.content) self.assertEqual(len(response['aaData']), 1) # do it again as a simple user # should not see anything self.client.login(username='******', password='******') res = self.client.get(reverse('auditlog_json')) self.assertEqual(res.status_code, 200) response = json.loads(res.content) # the response should be empty for a simple user response = json.loads(res.content) self.assertEqual(len(response['aaData']), 0) # but it sould have an entry for the superuser self.client.login(username='******', password='******') res = self.client.get(reverse('auditlog_json')) self.assertEqual(res.status_code, 200) response = json.loads(res.content) self.assertEqual(len(response['aaData']), 1)
def lock(request, instance): if (request.user.is_superuser or request.user.has_perm('ganeti.view_instances')): if instance: try: instance = Instance.objects.get(name=instance) except ObjectDoesNotExist: raise Http404() instance_adminlock = instance.adminlock if request.method == 'POST': form = lockForm(request.POST) if form.is_valid(): adminlock = form.cleaned_data['lock'] if adminlock: auditlog = auditlog_entry(request, "Lock Instance", instance, instance.cluster.slug) # set the cache for a week cache.set('instances:%s' % (instance.name), True, 60 * 60 * 24 * 7) jobid = instance.cluster.tag_instance( instance.name, ["%s:adminlock" % settings.GANETI_TAG_PREFIX]) auditlog.update(job_id=jobid) if adminlock is False: cache.delete('instances:%s' % (instance.name)) auditlog = auditlog_entry(request, "Unlock Instance", instance, instance.cluster.slug) jobid = instance.cluster.untag_instance( instance.name, ["%s:adminlock" % settings.GANETI_TAG_PREFIX]) auditlog.update(job_id=jobid) res = {'result': 'success'} return HttpResponse(json.dumps(res), content_type='application/json') else: return render(request, 'tagging/lock.html', { 'form': form, 'instance': instance }) elif request.method == 'GET': form = lockForm(initial={'lock': instance_adminlock}) return render(request, 'tagging/lock.html', { 'form': form, 'instance': instance }) else: return HttpResponseRedirect(reverse('user-instances'))
def reboot(request, cluster_slug, instance): cluster = get_object_or_404(Cluster, slug=cluster_slug) auditlog = auditlog_entry(request, "Reboot", instance, cluster_slug) jobid = cluster.reboot_instance(instance) auditlog.update(job_id=jobid) action = {'action': _("Please wait... rebooting")} clear_cluster_user_cache(request.user.username, cluster_slug) return HttpResponse(json.dumps(action))
def startup(request, cluster_slug, instance): cluster = get_object_or_404(Cluster, slug=cluster_slug) auditlog = auditlog_entry(request, 'Start', instance, cluster_slug) jobid = cluster.startup_instance(instance) auditlog.update(job_id=jobid) action = {'action': _('Please wait... starting-up')} clear_cluster_user_cache(request.user.username, cluster_slug) return HttpResponse(json.dumps(action))
def reboot(request, cluster_slug, instance): cluster = get_object_or_404(Cluster, slug=cluster_slug) auditlog = auditlog_entry(request, "Reboot", instance, cluster_slug) jobid = cluster.reboot_instance(instance) auditlog.update(job_id=jobid) action = {"action": _("Please wait... rebooting")} clear_cluster_user_cache(request.user.username, cluster_slug) return HttpResponse(json.dumps(action))
def isolate(request, instance): if (request.user.is_superuser or request.user.has_perm('ganeti.view_instances')): if instance: try: instance = Instance.objects.get(name=instance) except ObjectDoesNotExist: raise Http404 instance_isolate = instance.isolate if request.method == 'POST': form = isolateForm(request.POST) if form.is_valid(): isolate = form.cleaned_data['isolate'] if isolate: auditlog = auditlog_entry(request, "Isolate", instance, instance.cluster.slug) jobid = instance.cluster.tag_instance( instance.name, ["%s:isolate" % settings.GANETI_TAG_PREFIX]) auditlog.update(job_id=jobid) instance.cluster.migrate_instance(instance.name) if not isolate: auditlog = auditlog_entry(request, "De-Isolate", instance, instance.cluster.slug) jobid = instance.cluster.untag_instance( instance.name, ["%s:isolate" % settings.GANETI_TAG_PREFIX]) auditlog.update(job_id=jobid) instance.cluster.migrate_instance(instance.name) res = {'result': 'success'} return HttpResponse(json.dumps(res), content_type='application/json') else: return render(request, 'tagging/isolate.html', { 'form': form, 'instance': instance }) elif request.method == 'GET': form = isolateForm(initial={'isolate': instance_isolate}) return render(request, 'tagging/isolate.html', { 'form': form, 'instance': instance }) else: return HttpResponseRedirect(reverse('user-instances'))
def startup(request, cluster_slug, instance): cluster = get_object_or_404(Cluster, slug=cluster_slug) auditlog = auditlog_entry(request, 'Start', instance, cluster_slug) jobid = cluster.startup_instance(instance) auditlog.update(job_id=jobid) action = { 'action': _('Please wait... starting-up') } clear_cluster_user_cache(request.user.username, cluster_slug) return HttpResponse(json.dumps(action))
def reinstalldestreview(request, application_hash, action_id): action_id = int(action_id) instance = None if action_id not in [1, 2, 3, 4]: return HttpResponseRedirect(reverse('user-instances')) # Normalize before trying anything with it. activation_key = application_hash.lower() try: action = InstanceAction.objects.get( activation_key=activation_key, action=action_id ) except InstanceAction.DoesNotExist: activated = True return render( request, 'instances/verify_action.html', { 'action': action_id, 'activated': activated, 'instance': instance, 'hash': application_hash } ) if action_id == 4: instance_action = InstanceAction.objects.activate_request( application_hash ) user = User.objects.get(username=request.user) user.email = action.action_value user.save() messages.add_message( request, messages.INFO, _('Mail changed succesfully.') ) return HttpResponseRedirect(reverse('profile')) instance = action.instance cluster = action.cluster if not request.user.userprofile.is_owner(cluster.get_instance(instance)): action = '' if action_id is 1: action = 'reinstall' elif action_id is 3: action = 'rename' elif action_id is 2: action = 'destroy' auditlog_entry(request, 'Unauthorized ' + action + ' attempt', instance, cluster.slug, True, False) mail_unauthorized_action( action, instance, request.user.userprofile.user.username ) raise PermissionDenied action_value = action.action_value activated = False try: instance_object = Instance.objects.get(name=instance) except: # This should occur only when user changes email pass if action.activation_key_expired(): activated = True if request.method == 'POST': if not activated: instance_action = InstanceAction.objects.activate_request( application_hash ) if not instance_action: return render( request, 'instances/verify_action.html', { 'action': action_id, 'activated': activated, 'instance': instance, 'hash': application_hash } ) if action_id in [1, 2, 3]: auditlog = auditlog_entry(request, "", instance, cluster.slug, save=False) if action_id == 1: auditlog.update(action="Reinstall") jobid = cluster.reinstall_instance(instance, instance_action) # in case there is no selected os if jobid: auditlog.update(job_id=jobid) else: return HttpResponseServerError() if action_id == 2: auditlog.update(action="Destroy") jobid = cluster.destroy_instance(instance) auditlog.update(job_id=jobid) if action_id == 3: # As rename causes cluster lock we perform some cache # engineering on the cluster instances, # nodes and the users cache refresh_cluster_cache(cluster, instance) auditlog.update(action="Rename to %s" % action_value) jobid = cluster.rename_instance(instance, action_value) auditlog.update(job_id=jobid) activated = True return HttpResponseRedirect(reverse('user-instances')) else: return render( request, 'instances/verify_action.html', { 'action': action_id, 'activated': activated, 'instance': instance, 'instance_object': instance_object, 'hash': application_hash } ) elif request.method == 'GET': return render( request, 'instances/verify_action.html', { 'instance': instance, 'instance_object': instance_object, 'action': action_id, 'action_value': action_value, 'cluster': cluster, 'activated': activated, 'hash': application_hash } ) else: return HttpResponseRedirect(reverse('user-instances'))
def instance(request, cluster_slug, instance): cluster = get_object_or_404(Cluster, slug=cluster_slug) instance = cluster.get_instance_or_404(instance) if request.method == 'POST': configform = InstanceConfigForm(request.POST) if configform.is_valid(): needs_reboot = False # The instance needs reboot if any of the hvparams have changed. if configform.cleaned_data['cdrom_type'] == 'none': configform.cleaned_data['cdrom_image_path'] = "" for key, val in configform.cleaned_data.items(): if key == "cdrom_type": continue if key == "whitelist_ip": continue if configform.cleaned_data[key] != instance.hvparams[key]: if instance.admin_state: needs_reboot = True if configform.cleaned_data['cdrom_type'] == 'none': configform.cleaned_data['cdrom_image_path'] = "" elif (configform.cleaned_data['cdrom_image_path'] != instance.hvparams['cdrom_image_path']): # This should be an http URL if (not ( configform.cleaned_data['cdrom_image_path'].startswith( 'http://') or configform. cleaned_data['cdrom_image_path'].startswith('https://') or configform.cleaned_data['cdrom_image_path'] == "")): # Remove this, we don't want them to # be able to read local files del configform.cleaned_data['cdrom_image_path'] data = {} whitelistip = None for key, val in configform.cleaned_data.items(): if key == "cdrom_type": continue if key == "whitelist_ip": whitelistip = val if len(val) == 0: whitelistip = None continue data[key] = val auditlog = auditlog_entry( request, "Setting %s" % (", ".join( ["%s:%s" % (key, value) for key, value in data.items()])), instance, cluster_slug) jobid = instance.set_params(hvparams=data) auditlog.update(job_id=jobid) if needs_reboot: instance.cluster.tag_instance( instance.name, ["%s:needsreboot" % (settings.GANETI_TAG_PREFIX)]) if instance.whitelistip and whitelistip is None: auditlog = auditlog_entry( request, "Remove whitelist %s" % instance.whitelistip, instance, cluster_slug) jobid = instance.cluster.untag_instance( instance.name, [ "%s:whitelist_ip:%s" % (settings.GANETI_TAG_PREFIX, instance.whitelistip) ]) auditlog.update(job_id=jobid) instance.cluster.migrate_instance(instance.name) if whitelistip: if instance.whitelistip: auditlog = auditlog_entry( request, "Remove whitelist %s" % instance.whitelistip, instance, cluster_slug) jobid = instance.cluster.untag_instance( instance.name, [ "%s:whitelist_ip:%s" % (settings.GANETI_TAG_PREFIX, instance.whitelistip) ]) auditlog.update(job_id=jobid) auditlog = auditlog_entry(request, "Add whitelist %s" % whitelistip, instance, cluster_slug) jobid = instance.cluster.tag_instance(instance.name, [ "%s:whitelist_ip:%s" % (settings.GANETI_TAG_PREFIX, whitelistip) ]) auditlog.update(job_id=jobid) instance.cluster.migrate_instance(instance.name) # Prevent form re-submission via browser refresh return HttpResponseRedirect( reverse('instance-detail', kwargs={ 'cluster_slug': cluster_slug, 'instance': instance })) else: if 'cdrom_image_path' in instance.hvparams: if instance.hvparams['cdrom_image_path']: instance.hvparams['cdrom_type'] = 'iso' else: instance.hvparams['cdrom_type'] = 'none' else: instance.hvparams['cdrom_type'] = 'none' if instance.whitelistip: instance.hvparams['whitelist_ip'] = instance.whitelistip else: instance.hvparams['whitelist_ip'] = '' configform = InstanceConfigForm(instance.hvparams) instance.cpu_url = reverse('graph', args=(cluster.slug, instance.name, 'cpu-ts')) instance.net_url = [] for (nic_i, link) in enumerate(instance.nic_links): instance.net_url.append( reverse('graph', args=(cluster.slug, instance.name, 'net-ts', '/eth%s' % nic_i))) instance.netw = [] try: instance.osname = get_os_details(instance.osparams['img_id']).get( 'description', instance.osparams['img_id']) except Exception: try: instance.osname = instance.osparams['img_id'] except Exception: instance.osname = instance.os instance.node_group_locked = instance.pnode in instance.cluster.locked_nodes_from_nodegroup( ) for (nic_i, link) in enumerate(instance.nic_links): if instance.nic_ips[nic_i] is None: instance.netw.append("%s" % (instance.nic_links[nic_i])) else: instance.netw.append( "%s@%s" % (instance.nic_ips[nic_i], instance.nic_links[nic_i])) if instance.isolate and instance.whitelistip is None: djmessages.add_message( request, msgs.ERROR, "Your instance is isolated from the network and" + " you have not set an \"Allowed From\" address." + " To access your instance from a specific network range," + " you can set it via the instance configuration form") if instance.needsreboot: djmessages.add_message( request, msgs.ERROR, "You have modified one or more of your instance's core " + "configuration components (any of network adapter, hard" + " disk type, boot device, cdrom). In order for these changes " + "to take effect, you need to <strong>Reboot</strong>" + " your instance.", extra_tags='safe') ret_dict = {'cluster': cluster, 'instance': instance} if not request.user.has_perm('ganeti.view_instances') or ( request.user.is_superuser or request.user in instance.users or set.intersection(set(request.user.groups.all()), set(instance.groups))): ret_dict['configform'] = configform return render(request, 'instances/instance.html', ret_dict)
def isolate(request, instance): if ( request.user.is_superuser or request.user.has_perm('ganeti.view_instances') ): if instance: try: instance = Instance.objects.get(name=instance) except ObjectDoesNotExist: raise Http404 instance_isolate = instance.isolate if request.method == 'POST': form = isolateForm(request.POST) if form.is_valid(): isolate = form.cleaned_data['isolate'] if isolate: auditlog = auditlog_entry( request, "Isolate", instance, instance.cluster.slug ) jobid = instance.cluster.tag_instance( instance.name, ["%s:isolate" % settings.GANETI_TAG_PREFIX] ) auditlog.update(job_id=jobid) instance.cluster.migrate_instance(instance.name) if not isolate: auditlog = auditlog_entry( request, "De-Isolate", instance, instance.cluster.slug ) jobid = instance.cluster.untag_instance( instance.name, ["%s:isolate" % settings.GANETI_TAG_PREFIX] ) auditlog.update(job_id=jobid) instance.cluster.migrate_instance(instance.name) res = {'result': 'success'} return HttpResponse( json.dumps(res), content_type='application/json' ) else: return render( request, 'tagging/isolate.html', { 'form': form, 'instance': instance } ) elif request.method == 'GET': form = isolateForm(initial={'isolate': instance_isolate}) return render( request, 'tagging/isolate.html', { 'form': form, 'instance': instance } ) else: return HttpResponseRedirect(reverse('user-instances'))
def lock(request, instance): if ( request.user.is_superuser or request.user.has_perm('ganeti.view_instances') ): if instance: try: instance = Instance.objects.get(name=instance) except ObjectDoesNotExist: raise Http404() instance_adminlock = instance.adminlock if request.method == 'POST': form = lockForm(request.POST) if form.is_valid(): adminlock = form.cleaned_data['lock'] if adminlock: auditlog = auditlog_entry( request, "Lock Instance", instance, instance.cluster.slug ) # set the cache for a week cache.set('instances:%s' % (instance.name), True, 60 * 60 * 24 * 7) jobid = instance.cluster.tag_instance( instance.name, ["%s:adminlock" % settings.GANETI_TAG_PREFIX] ) auditlog.update(job_id=jobid) if adminlock is False: cache.delete('instances:%s' % (instance.name)) auditlog = auditlog_entry( request, "Unlock Instance", instance, instance.cluster.slug ) jobid = instance.cluster.untag_instance( instance.name, ["%s:adminlock" % settings.GANETI_TAG_PREFIX] ) auditlog.update(job_id=jobid) res = { 'result': 'success' } return HttpResponse(json.dumps(res), content_type='application/json') else: return render( request, 'tagging/lock.html', { 'form': form, 'instance': instance } ) elif request.method == 'GET': form = lockForm(initial={'lock': instance_adminlock}) return render( request, 'tagging/lock.html', { 'form': form, 'instance': instance } ) else: return HttpResponseRedirect(reverse('user-instances'))
def instance(request, cluster_slug, instance): cluster = get_object_or_404(Cluster, slug=cluster_slug) instance = cluster.get_instance_or_404(instance) if request.method == 'POST': configform = InstanceConfigForm(request.POST) if configform.is_valid(): needs_reboot = False # The instance needs reboot if any of the hvparams have changed. if configform.cleaned_data['cdrom_type'] == 'none': configform.cleaned_data['cdrom_image_path'] = "" for key, val in configform.cleaned_data.items(): if key == "cdrom_type": continue if key == "whitelist_ip": continue if configform.cleaned_data[key] != instance.hvparams[key]: if instance.admin_state: needs_reboot = True if configform.cleaned_data['cdrom_type'] == 'none': configform.cleaned_data['cdrom_image_path'] = "" elif (configform.cleaned_data['cdrom_image_path'] != instance.hvparams['cdrom_image_path']): # This should be an http URL if ( not ( configform.cleaned_data['cdrom_image_path'].startswith( 'http://' ) or configform.cleaned_data['cdrom_image_path'].startswith( 'https://' ) or configform.cleaned_data['cdrom_image_path'] == "" ) ): # Remove this, we don't want them to # be able to read local files del configform.cleaned_data['cdrom_image_path'] data = {} whitelistip = None for key, val in configform.cleaned_data.items(): if key == "cdrom_type": continue if key == "whitelist_ip": whitelistip = val if len(val) == 0: whitelistip = None continue data[key] = val auditlog = auditlog_entry( request, "Setting %s" % ( ", ".join( [ "%s:%s" % (key, value) for key, value in data.iteritems() ] ) ), instance, cluster_slug ) jobid = instance.set_params(hvparams=data) auditlog.update(job_id=jobid) if needs_reboot: instance.cluster.tag_instance( instance.name, ["%s:needsreboot" % (settings.GANETI_TAG_PREFIX)] ) if instance.whitelistip and whitelistip is None: auditlog = auditlog_entry( request, "Remove whitelist %s" % instance.whitelistip, instance, cluster_slug ) jobid = instance.cluster.untag_instance( instance.name, ["%s:whitelist_ip:%s" % ( settings.GANETI_TAG_PREFIX, instance.whitelistip )] ) auditlog.update(job_id=jobid) instance.cluster.migrate_instance(instance.name) if whitelistip: if instance.whitelistip: auditlog = auditlog_entry( request, "Remove whitelist %s" % instance.whitelistip, instance, cluster_slug ) jobid = instance.cluster.untag_instance( instance.name, ["%s:whitelist_ip:%s" % ( settings.GANETI_TAG_PREFIX, instance.whitelistip )]) auditlog.update(job_id=jobid) auditlog = auditlog_entry( request, "Add whitelist %s" % whitelistip, instance, cluster_slug ) jobid = instance.cluster.tag_instance( instance.name, ["%s:whitelist_ip:%s" % (settings.GANETI_TAG_PREFIX, whitelistip)] ) auditlog.update(job_id=jobid) instance.cluster.migrate_instance(instance.name) # Prevent form re-submission via browser refresh return HttpResponseRedirect( reverse( 'instance-detail', kwargs={'cluster_slug': cluster_slug, 'instance': instance} ) ) else: if 'cdrom_image_path' in instance.hvparams.keys(): if instance.hvparams['cdrom_image_path']: instance.hvparams['cdrom_type'] = 'iso' else: instance.hvparams['cdrom_type'] = 'none' else: instance.hvparams['cdrom_type'] = 'none' if instance.whitelistip: instance.hvparams['whitelist_ip'] = instance.whitelistip else: instance.hvparams['whitelist_ip'] = '' configform = InstanceConfigForm(instance.hvparams) instance.cpu_url = reverse( 'graph', args=(cluster.slug, instance.name, 'cpu-ts') ) instance.net_url = [] for (nic_i, link) in enumerate(instance.nic_links): instance.net_url.append( reverse( 'graph', args=(cluster.slug, instance.name, 'net-ts', '/eth%s' % nic_i) ) ) instance.netw = [] try: instance.osname = get_os_details( instance.osparams['img_id']).get( 'description', instance.osparams['img_id'] ) except Exception: try: instance.osname = instance.osparams['img_id'] except Exception: instance.osname = instance.os instance.node_group_locked = instance.pnode in instance.cluster.locked_nodes_from_nodegroup() for (nic_i, link) in enumerate(instance.nic_links): if instance.nic_ips[nic_i] is None: instance.netw.append("%s" % (instance.nic_links[nic_i])) else: instance.netw.append("%s@%s" % ( instance.nic_ips[nic_i], instance.nic_links[nic_i]) ) if instance.isolate and instance.whitelistip is None: djmessages.add_message( request, msgs.ERROR, "Your instance is isolated from the network and" + " you have not set an \"Allowed From\" address." + " To access your instance from a specific network range," + " you can set it via the instance configuration form" ) if instance.needsreboot: djmessages.add_message( request, msgs.ERROR, "You have modified one or more of your instance's core " + "configuration components (any of network adapter, hard" + " disk type, boot device, cdrom). In order for these changes " + "to take effect, you need to <strong>Reboot</strong>" + " your instance.", extra_tags='safe' ) ret_dict = {'cluster': cluster, 'instance': instance } if not request.user.has_perm('ganeti.view_instances') or ( request.user.is_superuser or request.user in instance.users or set.intersection(set(request.user.groups.all()), set(instance.groups)) ): ret_dict['configform'] = configform return render( request, 'instances/instance.html', ret_dict )