Skip to content

Commit

Permalink
feat(gui): use middleware to wipe disks
Browse files Browse the repository at this point in the history
Ticket:	#31215
  • Loading branch information
william-gr committed Apr 3, 2018
1 parent d00fae6 commit 42df592
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 165 deletions.
89 changes: 0 additions & 89 deletions gui/middleware/notifier.py
Expand Up @@ -3555,95 +3555,6 @@ def staticroute_delete(self, sr):
if p1.wait() != 0:
raise MiddlewareError("Failed to remove the route %s" % sr.sr_destination)

def __get_geoms_recursive(self, prvid):
"""
Get _ALL_ geom nodes that depends on a given provider
"""
doc = self._geom_confxml()
geoms = []
for c in doc.xpath("//consumer/provider[@ref = '%s']" % (prvid, )):
geom = c.getparent().getparent()
if geom.tag != 'geom':
continue
geoms.append(geom)
for prov in geom.xpath('./provider'):
geoms.extend(self.__get_geoms_recursive(prov.attrib.get('id')))

return geoms

def disk_get_consumers(self, devname):
doc = self._geom_confxml()
geom = doc.xpath("//class[name = 'DISK']/geom[name = '%s']" % (
devname,
))
if geom:
provid = geom[0].xpath("./provider/@id")[0]
else:
raise ValueError("Unknown disk %s" % (devname, ))
return self.__get_geoms_recursive(provid)

def _do_disk_wipe_quick(self, devname):
pipe = self._pipeopen("dd if=/dev/zero of=/dev/%s bs=1m count=32" % (devname, ))
err = pipe.communicate()[1]
if pipe.returncode != 0:
raise MiddlewareError(
"Failed to wipe %s: %s" % (devname, err)
)
try:
p1 = self._pipeopen("diskinfo %s" % (devname, ))
size = int(int(re.sub(r'\s+', ' ', p1.communicate()[0]).split()[2]) / (1024))
except:
log.error("Unable to determine size of %s", devname)
else:
pipe = self._pipeopen("dd if=/dev/zero of=/dev/%s bs=1m oseek=%s" % (
devname,
int(size / 1024) - 32,
))
pipe.communicate()

def disk_wipe(self, devname, mode='quick'):
if mode == 'quick':
doc = self._geom_confxml()
parts = [node.text for node in doc.xpath("//class[name = 'PART']/geom[name = '%s']/provider/name" % devname)]
"""
Wipe beginning and the end of every partition
This should erase ZFS label and such to prevent further errors on replace
"""
for part in parts:
self._do_disk_wipe_quick(part)
self.__gpt_unlabeldisk(devname)
self._do_disk_wipe_quick(devname)

elif mode in ('full', 'fullrandom'):
libc = ctypes.cdll.LoadLibrary("libc.so.7")
omask = (ctypes.c_uint32 * 4)(0, 0, 0, 0)
mask = (ctypes.c_uint32 * 4)(0, 0, 0, 0)
pmask = ctypes.pointer(mask)
pomask = ctypes.pointer(omask)
libc.sigprocmask(signal.SIGQUIT, pmask, pomask)

self.__gpt_unlabeldisk(devname)
stderr = open('/var/tmp/disk_wipe_%s.progress' % (devname, ), 'w+')
stderr.flush()
pipe = subprocess.Popen([
"dd",
"if=/dev/zero" if mode == 'full' else "if=/dev/random",
"of=/dev/%s" % (devname, ),
"bs=1m",
], stdout=subprocess.PIPE, stderr=stderr, encoding='utf8')
with open('/var/tmp/disk_wipe_%s.pid' % (devname, ), 'w') as f:
f.write(str(pipe.pid))
pipe.communicate()
stderr.seek(0)
err = stderr.read()
libc.sigprocmask(signal.SIGQUIT, pomask, None)
if pipe.returncode != 0 and err.find("end of device") == -1:
raise MiddlewareError(
"Failed to wipe %s: %s" % (devname, err)
)
else:
raise ValueError("Unknown mode %s" % (mode, ))

def dataset_init_unix(self, dataset):
"""path = "/mnt/%s" % dataset"""
pass
Expand Down
27 changes: 17 additions & 10 deletions gui/middleware/util.py
@@ -1,5 +1,6 @@
import json
import requests
import time

from freenasUI.middleware.client import client

Expand All @@ -18,6 +19,21 @@ def run_alerts():
c.call('alert.process_alerts')


def wait_job(client, job_id):
assert isinstance(job_id, int)
while True:
job = client.call('core.get_jobs', [('id', '=', job_id)])
if job:
job = job[0]
if job['state'] == 'FAILED':
raise JobFailed(job['error'])
elif job['state'] == 'ABORTED':
raise JobAborted()
elif job['state'] == 'SUCCESS':
return job
time.sleep(0.5)


def upload_job_and_wait(fileobj, method_name, *args):

with client as c:
Expand All @@ -36,13 +52,4 @@ def upload_job_and_wait(fileobj, method_name, *args):
},
)
job_id = r.json()['job_id']
while True:
job = c.call('core.get_jobs', [('id', '=', job_id)])
if job:
job = job[0]
if job['state'] == 'FAILED':
raise JobFailed(job['error'])
elif job['state'] == 'ABORTED':
raise JobAborted()
elif job['state'] == 'SUCCESS':
return job
return wait_job(c, job_id)
18 changes: 15 additions & 3 deletions gui/storage/forms.py
Expand Up @@ -2601,13 +2601,25 @@ class DiskWipeForm(Form):
method = forms.ChoiceField(
label=_("Method"),
choices=(
("quick", _("Quick")),
("full", _("Full with zeros")),
("fullrandom", _("Full with random data")),
("QUICK", _("Quick")),
("FULL", _("Full with zeros")),
("FULL_RANDOM", _("Full with random data")),
),
widget=forms.widgets.RadioSelect(),
)

def __init__(self, *args, **kwargs):
self.disk = kwargs.pop('disk')
super().__init__(*args, **kwargs)

def clean(self):
with client as c:
if self.disk in c.call('disk.get_reserved'):
self._errors['__all__'] = self.error_class([
_('The disk %s is currently in use and cannot be wiped.') % self.disk
])
return self.cleaned_data


class CreatePassphraseForm(Form):

Expand Down
95 changes: 33 additions & 62 deletions gui/storage/views.py
Expand Up @@ -29,8 +29,6 @@
import logging
import os
import re
import signal
import subprocess
import urllib.request, urllib.parse, urllib.error
from time import sleep

Expand All @@ -44,18 +42,19 @@

from freenasUI.common import humanize_size
from freenasUI.common.pipesubr import pipeopen
from freenasUI.common.system import is_mounted
from freenasUI.freeadmin.apppool import appPool
from freenasUI.freeadmin.views import JsonResp
from freenasUI.middleware import zfs
from freenasUI.middleware.client import client, ClientException
from freenasUI.middleware.exceptions import MiddlewareError
from freenasUI.middleware.notifier import notifier
from freenasUI.middleware.util import JobAborted, JobFailed, wait_job
from freenasUI.system.models import Advanced
from freenasUI.services.exceptions import ServiceFailed
from freenasUI.services.models import iSCSITargetExtent
from freenasUI.storage import forms, models

DISK_WIPE_JOB_ID = None
log = logging.getLogger('storage.views')


Expand Down Expand Up @@ -844,87 +843,59 @@ def multipath_status_json(request):


def disk_wipe(request, devname):
global DISK_WIPE_JOB_ID

form = forms.DiskWipeForm()
if request.method == "POST":
form = forms.DiskWipeForm(request.POST)
form = forms.DiskWipeForm(request.POST, disk=devname)
if form.is_valid():
mounted = []
for geom in notifier().disk_get_consumers(devname):
gname = geom.xpath("./name")[0].text
dev = "/dev/%s" % (gname, )
if dev not in mounted and is_mounted(device=dev):
mounted.append(dev)
for vol in models.Volume.objects.all():
if devname in vol.get_disks():
mounted.append(vol.vol_name)
if mounted:
form._errors['__all__'] = form.error_class([
"Umount the following mount points before proceeding:"
"<br /> %s" % (
'<br /> '.join(mounted),
)
])
else:
notifier().disk_wipe(devname, form.cleaned_data['method'])
return JsonResp(
request,
message=_("Disk successfully wiped"))
DISK_WIPE_JOB_ID = None
try:
with client as c:
DISK_WIPE_JOB_ID = c.call('disk.wipe', devname, form.cleaned_data['method'])
wait_job(c, DISK_WIPE_JOB_ID)
except JobAborted:
raise MiddlewareError(_('Disk wipe job was aborted'))
except JobFailed as e:
raise MiddlewareError(_('Disk wipe job failed: %s') % str(e.value))
return JsonResp(
request,
message=_("Disk successfully wiped"))

return JsonResp(request, form=form)

form = forms.DiskWipeForm(disk=devname)

return render(request, "storage/disk_wipe.html", {
'devname': devname,
'form': form,
})


def disk_wipe_progress(request, devname):
global DISK_WIPE_JOB_ID
details = 'Starting Disk Wipe'
indeterminate = True
progress = 0
step = 1
finished = False
error = False
pidfile = '/var/tmp/disk_wipe_%s.pid' % (devname, )
if not os.path.exists(pidfile):
if not DISK_WIPE_JOB_ID:
return HttpResponse('new Object({state: "starting"});')

with open(pidfile, 'r') as f:
pid = f.read()

try:
os.kill(int(pid), signal.SIGINFO)
received = 0
size = 0
indeterminate = False
with open('/var/tmp/disk_wipe_%s.progress' % (devname, ), 'r') as f:
data = f.read()
transf = re.findall(
r'^(?P<bytes>\d+) bytes transferred.*',
data,
re.M)
if transf:
pipe = subprocess.Popen([
"/usr/sbin/diskinfo",
devname,
], stdout=subprocess.PIPE, encoding='utf8')
output = pipe.communicate()[0]
size = output.split()[2]
received = transf[-1]
details = 'Wiping Disk...'
if received == size:
finished = True
progress = 100
else:
try:
progress = int(float(received) / float(size) * 100)
except Exception:
pass
with client as c:
job = c.call('core.get_jobs', [['id', '=', DISK_WIPE_JOB_ID]])

except Exception as e:
log.warn("Could not check for disk wipe progress: %s", e)
indeterminate = True
if not job:
return HttpResponse('new Object({state: "starting"});')
job = job[0]

if job['state'] == 'FAILED':
error = True
details = job['error']
elif job['state'] == 'RUNNING':
progress = job['progress']['percent'] or 0
elif job['state'] == 'SUCCESS':
finished = True

data = {
'error': error,
Expand Down
2 changes: 1 addition & 1 deletion gui/templates/storage/disk_wipe.html
Expand Up @@ -14,7 +14,7 @@
steps: [
{"label": gettext("Wiping Disk")},
],
poolUrl: '/storage/disks/wipe/{{ devname }}/progress/',
poolUrl: '/legacy/storage/disks/wipe/{{ devname }}/progress/',
mode: 'single',
fileUpload: false
}
Expand Down

0 comments on commit 42df592

Please sign in to comment.