Skip to content

Instantly share code, notes, and snippets.

@ytjohn
Created February 24, 2017 00:20
Show Gist options
  • Save ytjohn/5cfb9ad2fc0aec14a4f7ac658f72708a to your computer and use it in GitHub Desktop.
Save ytjohn/5cfb9ad2fc0aec14a4f7ac658f72708a to your computer and use it in GitHub Desktop.
vs secure erase
#!/usr/bin/env python
# Copyright 2016, EMC, Inc.
# -*- coding: UTF-8 -*-
"""
This script is to do Secure Erase (SE) on a compute node
Four methods/tools are integrated in this scripts
A log file will be created for each disk to be erased named after disk name, like sdx.log
"""
# This is a modified version for Rubicon. It hard codes process count to 63
# it comes from this commit: https://github.com/RackHD/on-http/blob/9e7713e470314501915ab88522091d861a1d787b/data/templates/secure_erase.py
import os
import sys
import re
import subprocess
import argparse
import time
import json
from multiprocessing import Pool
from multiprocessing import cpu_count
from filecmp import cmp as file_compare
ARG_PARSER = argparse.ArgumentParser(description='RackHD secure-erase argument')
ARG_PARSER.add_argument("-i", action="store", default='undefined', type=str,
help="Secure erase taskId")
ARG_PARSER.add_argument("-s", action="store", default='', type=str,
help="RackHD host server address")
ARG_PARSER.add_argument("-d", action="append", default=[], type=str,
help="Disks to be erased with arguments")
ARG_PARSER.add_argument("-v", action="store", default="lsi", type=str,
help="RAID controller vendor info")
ARG_PARSER.add_argument("-t", action="store", type=str,
help="Specify secure erase tool, "
"scrub, hdpram, sg_format, sg_sanitize are supported")
ARG_PARSER.add_argument("-o", action="store", type=str,
help='Specify SE options. '
'For SG_format SE options, "0"/"1" are supported, '
'stands for erasing/not erasing GLIST.\n '
'For scrub SE options, "nnsa", "dod", "fillzero", '
'"random", "random2", "fillff", "gutmann", "schneier", '
'"fastold", "pfitzner7", "pfitzner33", "usarmy", '
'"old", "fastold" and "custom=string" are supported.'
'Please read scrub man page for more details. \n'
'For sg_sanitize SE options, "block", "crypto", "fail" are supported. '
'Overwrite option is not supported at current stage. '
'Please read tool man page for more details \n'
'For hdparm, "security-erase" and "security-erase-enhanced" \n'
'are supported')
ARG_LIST = ARG_PARSER.parse_args()
RAID_VENDOR_LIST = {
"lsi": "/opt/MegaRAID/storcli/storcli64",
"dell": "/opt/MegaRAID/perccli/perccli64"
}
SE_PASSWORD = "rackhd_secure_erase"
FRONT_SKIP = "4096"
HDPARM_RETRY_EXITCODES = [5]
COMMAND_LOG_MARKER = "\n==========================" \
"==========================\n"
if os.getcwd()[-1] != '/':
PATH = os.getcwd() + "/"
else:
PATH = os.getcwd()
class Progress:
"""
Secure erase job progress class.
"""
def __init__(self, disks, path, parameters):
self.parameters = { # parameters parsed from command
"taskId": parameters["taskId"], # task id for notification
"address": parameters["address"], # rackhd server address
"tool": parameters["tool"], # erase tool to be used
"option": parameters["option"] # erase command arguments options
}
self.disk_list = disks # disks to be erased
self.interval = 60 # erase progress polling interval in seconds
self.percent = 0.0 # last progress percent buffer
self.duration = {} # erase duration for each disks
self.path = path # log file path
def scrub_parser(self, drive):
"""
Secure erase job progress parser for scrub tool.
:param drive: drive name
:return: a float digital of percentage
"""
# Scrub version 2.5.2-2 example:
# scrub /dev/sdf
# scrub: using NNSA NAP-14.1-C patterns
# scrub: please verify that device size below is correct!
# scrub: scrubbing /dev/sdf1 1995650048 bytes (~1GB)
# scrub: random |................................................|
# scrub: random |................................................|
# scrub: 0x00 |................................................|
# scrub: verify |................................................|
# Maximum dot count for each pass
log = open(self.path + drive + '.log', 'r')
MAX_DOT_COUNT = 50
# Scrub pass counts for different scrub methods, default pass count is 1
pass_counts = {"nnsa": 4, "dod": 4, "gutmann": 35, "schneier":7, "pfitzner7":7,
"pfitzner33": 33, "usarmy": 3, "random2": 2, "old": 6, "fastold": 5}
pass_count = pass_counts[self.parameters["option"]]
line_count = 0
dot_count = 0
patterna = re.compile("^scrub: \w{4,6}\s*\|\.+\|$")
patternb = re.compile("^scrub: \w{4,6}\s*\|\.*$")
for line in log.readlines():
line = line.strip()
if patterna.match(line):
line_count += 1
elif patternb.match(line):
dot_count = len(line.split("|")[1])
percent = 100.00*(line_count*MAX_DOT_COUNT + dot_count)/(pass_count*MAX_DOT_COUNT)
log.close()
return percent
def __get_hdparm_duration(self, log):
"""
Get hdparm required secure erase time.
:param log: a file object of secure erase log
:return: required secure erase time indicated by hdparm tool
"""
pattern = re.compile("(\d{1,4})\s*min for SECURITY ERASE UNIT." +
"\s*(\d{1,4})\s*min for ENHANCED SECURITY ERASE UNIT.", re.I)
estimated_time = 0
for line in log.readlines():
line = line.strip()
match = pattern.match(line)
if match:
if self.parameters["option"] == "security-erase":
estimated_time = match.group(1)
else:
estimated_time = match.group(2)
return float(estimated_time)
def hdparm_parser(self, drive):
"""
Secure erase job progress parser for scrub tool.
:param drive: drive name
:return: a float digital of percentage
"""
log = open(self.path + drive + '.log', 'r')
percent = 0.0
if not self.duration.has_key(drive) or self.duration[drive] == 0:
self.duration[drive] = self.__get_hdparm_duration(log)
if self.duration[drive] != 0:
percent = self.percent + 100.0*self.interval/(self.duration[drive]*60.00)
# As hdparm SE duration is estimated, there might be percent larger than 100
# Let maximum precent to be 99.00 instead
if percent > 99.00:
percent = 99.00
log.close()
return percent
def __sg_requests_parser(self, drive):
"""
Secure erase job progress parser for sg_format and sg_sanitize tools.
:param drive: drive name
:return: a float digital of percentage
"""
patterna = re.compile("Progress indication:\s+(\d{1,3}\.\d{1,3})\% done")
cmd = ['sg_requests', '--progress', '/dev/' + drive]
for i in range(2):
try:
progress_output = subprocess.check_output(cmd, shell=False)
break
except subprocess.CalledProcessError:
progress_output = ''
if progress_output:
match = patterna.match(progress_output)
if match:
return float(match.group(1))
elif self.percent > 0:
return self.percent
return 0.00
def sg_format_parser(self, drive):
"""
Secure erase job progress parser for sg_format tool.
:param drive: drive name
:return: a float digital of percentage
"""
return self.__sg_requests_parser(drive)
def sg_sanitize_parser(self, drive):
"""
Secure erase job progress parser for sg_sanitize tool.
:param drive: drive name
:return: a float digital of percentage
"""
#return float('+inf')
return self.__sg_requests_parser(drive)
def __run(self):
"""
Get secure erase progress for secure erase task.
"""
parser_mapper = {
"hdparm": self.hdparm_parser,
"scrub": self.scrub_parser,
"sg_format": self.sg_format_parser,
"sg_sanitize": self.sg_sanitize_parser
}
disk_count = len(self.disk_list)
percentage_list = [0.0]*disk_count
erase_start_flags = [False]*disk_count
payload = {
"taskId": self.parameters["taskId"],
"progress": {
"value": 0,
"maximum": 100
}
}
counter = 0
total_percent = 0.00
if not self.parameters["address"]:
self.parameters["address"] = "http://172.31.128.1:9080/api/current/notification"
while True:
for (index, value) in enumerate(self.disk_list):
value = value.split("/")[-1]
if erase_start_flags[index]:
# Check if secure erase sub-progress is alive
command = 'ps aux | grep {} | grep {} | sed "/grep/d" | sed "/python/d"' \
.format(self.parameters["tool"], value)
erase_alive = subprocess.check_output(command, shell=True)
if not erase_alive:
percentage_list[index] = 100
else:
self.percent = percentage_list[index]
percentage_list[index] = parser_mapper[self.parameters["tool"]](value)
else:
erase_start_flags[index] = os.path.exists(self.path + value + '.log')
total_percent = sum(percentage_list)/disk_count
if total_percent == float('+inf'):
payload["progress"]["percentage"] = "Not Available"
else:
payload["progress"]["percentage"] = str("%.2f" % total_percent) + "%"
payload["progress"]["value"] = int(total_percent)
counter += 1
payload["progress"]["description"] = "This is the {}th polling with {}s interval" \
.format(str(counter), str(self.interval))
cmd = 'curl -X POST -H "Content-Type:application/json" ' \
'-d \'{}\' {}'.format(json.dumps(payload), self.parameters["address"])
try:
subprocess.call(cmd, shell=True)
except subprocess.CalledProcessError as err:
print err.output
if total_percent == 100:
break
time.sleep(self.interval)
def run(self):
"""
Get secure erase progress for secure erase task with try except to catch errors
"""
try:
self.__run()
except Exception as err:
return {"exit_code": -1, "message": err}
else:
return {"exit_code": 0, "message": "Progress succeeded"}
def create_jbod(disk_arg, raid_tool):
"""
Create JBOD for each physical disk under a virtual disk.
:param disk_arg: a dictionary contains disk argument
:param raid_tool: tools used for JBOD creation, storcli and perccli are supported
:return: a list contains disk OS names, like ["/dev/sda", "/dev/sdb", ...]
"""
for slot_id in disk_arg["slotIds"]:
cmd = [raid_tool, slot_id, "set", "jbod"]
subprocess.check_output(cmd, shell=False)
disk_list_with_jbod = []
# scsi id is used to map virtual disk to new JBOD
# scsi id is made up of adapter:scsi:dev:lun as below:
# adapter id [host]: controller ID, ascending from 0.
# Usually c0 for one controller in server Megaraid info
# scsi id [bus]: a number of 0-15.
# Usually different for RAID(like 2) and JBOD(like 0)
# device id [target]: displayed as DID in Megaraid for each physical drives.
# LUN id [LUN]: Logic Unit Numbers, LUN is not used for drive mapping
scsi_id_bits = disk_arg["scsiId"].split(":")
scsi_id_bits[-1] = "" # LUN ID is ignored
# map jbod to disk device name with JBOD
for device_id in disk_arg["deviceIds"]:
scsi_info = scsi_id_bits[:]
scsi_info[2] = str(device_id)
anti_patten = re.compile(":".join(scsi_info)) # anti-patten to exclude scsi id for RAID
scsi_info[1] = '[0-9]{1,2}' # scsi id should be a number of 0-15
patten = re.compile(":".join(scsi_info))
cmd = ["ls", "-l", "/dev/disk/by-path"]
# Retry 10 times in 1 second before OS can identify JBOD
for i in range(10):
time.sleep(0.1)
try:
lines = subprocess.check_output(cmd, shell=False).split("\n")
except subprocess.CalledProcessError:
continue
# example for "ls -l /dev/disk/by-path" console output
# total 0
# drwxr-xr-x 2 root root 300 May 19 03:15 ./
# drwxr-xr-x 5 root root 100 May 16 04:43 ../
# lrwxrwxrwx 1 root root 9 May 19 03:06 pci-0000:06:00.0-scsi-0:2:0:0 -> ../../sdf
# lrwxrwxrwx 1 root root 10 May 19 03:06 pci-0000:06:00.0-scsi-0:2:0:0-part1 ->
# ../../sdf1
# lrwxrwxrwx 1 root root 10 May 19 02:31 pci-0000:06:00.0-scsi-0:2:1:0 -> ../../sda
disk_name = ''
for line in lines:
if patten.search(line) and not anti_patten.search(line) and line.find("part") == -1:
disk_name = line.split("/")[-1]
break
if disk_name:
break
assert disk_name, "Disk OS name is not found for deviceId " + str(device_id)
disk_list_with_jbod.append("/dev/" + disk_name)
return disk_list_with_jbod
def convert_raid_to_jbod():
"""
To delete RAID and create JBOD for each physical disk of a virtual disk with RAID
:rtype : list
:return: a string includes all the disks to be erased
"""
disk_argument_list = []
# ARG_LIST.d should include at least following items as a string
# {
# "diskName": "/dev/sdx"
# "slotIds": ["/c0/e252/sx"]
# "deviceIds": [0]
# "virtualDisk": "/c0/vx"
# "scsiId": "0:0:0:0"
# }
for arg in ARG_LIST.d:
disk_argument_list.append(json.loads(arg))
assert disk_argument_list != [], "no disk arguments includes"
# Idenfity tools used for raid operation
raid_controller_vendor = ARG_LIST.v
assert raid_controller_vendor in RAID_VENDOR_LIST.keys(), \
"RAID controller vendor info is invalid"
raid_tool = RAID_VENDOR_LIST[raid_controller_vendor]
assert os.path.exists(raid_tool), "Overlay doesn't include tool path: " + raid_tool
disk_list_without_raid = []
for disk_argument in disk_argument_list:
# if virtualDisk doesn't exit, push disk directly into disk list
# https://rackhd.atlassian.net/browse/RAC-4099
if "virtualDisk" not in disk_argument.keys():
disk_argument["virtualDisk"] = ''
if not disk_argument["virtualDisk"]:
# disk_list_without_raid.append("/dev/" + disk_argument["diskName"])
disk_list_without_raid.append(disk_argument["diskName"])
else:
command = [raid_tool, "/c0", "set", "jbod=on"]
subprocess.check_output(command, shell=False)
command = [raid_tool, disk_argument["virtualDisk"], "del", "force"]
subprocess.check_output(command, shell=False)
disk_list_without_raid += create_jbod(disk_argument, raid_tool)
return disk_list_without_raid
def robust_check_call(cmd, log):
"""
Subprocess check_call module with try-except to catch CalledProcessError
Real time command output will be written to log file.
:rtype : dict
:param cmd: command option for subprocess.check_call, an array
:param log: an opened file object to store stdout and stderr
:return: a dict include exit_code and message info
"""
assert isinstance(cmd, list), "Input commands is not an array"
exit_status = {"exit_code": 0, "message": "check_call command succeeded"}
log.write(COMMAND_LOG_MARKER + "[" + " ".join(cmd) + "] output:\n")
log.flush() # Align logs
try:
exit_code = subprocess.check_call(cmd, shell=False, stdout=log, stderr=log)
except subprocess.CalledProcessError as exc:
exit_status["message"] = exc.output
exit_status["exit_code"] = exc.returncode
else:
exit_status["exit_code"] = exit_code
return exit_status
def robust_check_output(cmd, log):
"""
Subprocess check_output module with try-except to catch CalledProcessError
Command output will be written to log file after commands finished
:param cmd: command option for subprocess.check_output, an array
:param log: an opened file object to store stdout and stderr
:return: a dict include exit_code and command execution message
"""
assert isinstance(cmd, list), "Input commands is not an array"
exit_status = {"exit_code": 0, "message": "check_output command succeeded"}
log.write(COMMAND_LOG_MARKER + "[" + " ".join(cmd) + "] output:\n")
log.flush() # Align logs
try:
output = subprocess.check_output(cmd, shell=False, stderr=log)
except subprocess.CalledProcessError as exc:
exit_status["message"] = exc.output
exit_status["exit_code"] = exc.returncode
else:
exit_status["message"] = output
log.write(str(exit_status) + "\n")
log.flush() # Align logs
return exit_status
def get_disk_size(disk_name, log, mark_files):
"""
Get disk size and create empty mark files
:param disk_name: disk name that be copied data to.
:param log: an opened file object to store stdout and stderr
:return: a string of disk size
"""
# Filler drive size info from "fdisk -l /dev/sdx" commands
command = ["fdisk", "-l", disk_name]
disk_info = robust_check_output(command, log)
assert disk_info["exit_code"] == 0, "Can't get drive %s size info" % disk_name
output = disk_info["message"]
# Output example for the line contains disk size info:
# Disk /dev/sdx: 400.1 GB, 400088457216 bytes
disk_size = "0"
pattern = re.compile(r".*%s.* (\d{10,16}) bytes" % disk_name)
# Match disk with size from 1G to 1P
for line in output.split("\n"):
match_result = pattern.match(line)
if pattern.match(line):
disk_size = match_result.group(1)
break
assert disk_size != "0", "Disk size should not be 0"
for name in mark_files:
try:
os.mknod(name)
except OSError: # if file exits, ignore OSError
continue
return disk_size
def mark_on_disk(disk_name, log, flag, back_skip, mark_files):
"""
Copy 512 Bytes random data to specified disk address as a mark.
Or to read the marks from disk for verification
:param disk_name: disk name that be copied data to.
:param log: an opened file object to store stdout and stderr
:param flag: a flag to choose mark creation or verification action
:return:
"""
# Raw data will be restored in document mark_data, size is 512 byte
# Contents of mark_data will be write to both front/back end of disk addresses
commands = [
["dd", "if=/dev/urandom", "of=" + mark_files[0], "count=1"],
["dd", "if=" + mark_files[0], "of=" + disk_name, "seek=" + FRONT_SKIP, "count=1"],
["dd", "if=" + mark_files[0], "of=" + disk_name, "seek=" + back_skip, "count=1"],
["dd", "if=" + disk_name, "of=" + mark_files[1], "skip=" + FRONT_SKIP, "count=1"],
["dd", "if=" + disk_name, "of=" + mark_files[2], "skip=" + back_skip, "count=1"]
]
if not flag:
# Create marks
for command in commands:
exit_status = robust_check_call(command, log)
assert exit_status["exit_code"] == 0, "Command [ %s ] failed" % " ".join(command)
assert file_compare(mark_files[0], mark_files[1]), \
"Disk front mark data is not written correctly"
assert file_compare(mark_files[0], mark_files[2]), \
"Disk back mark data is not written correctly"
else:
# Verify marks
for command in commands[3:5]:
exit_status = robust_check_call(command, log)
assert exit_status["exit_code"] == 0, "Command [ %s ] failed" % " ".join(command)
assert not file_compare(mark_files[0], mark_files[1]), \
"Disk front mark data exists after erasing"
assert not file_compare(mark_files[0], mark_files[2]), \
"Disk back mark data exists after erasing"
return
def record_timestamp(log, action):
"""
Record erase start/complete timestamp for each disk
:param log: secure erase log file
:param action: secure erase start/complete string
"""
log.write(COMMAND_LOG_MARKER + action + " erase time is:\n")
log.write(time.strftime("%Y-%m-%d %X", time.localtime()) + "\n\n")
log.flush() # Align logs
return
def secure_erase_base(disk_name, cmd):
"""
Basic SE function
:param disk_name: disk to be erased
:param cmd: a list includes secure erase command argument
:return: a dict includes SE command exitcode and SE message
"""
name = disk_name.split("/")[-1]
log_file = name + ".log" # log file for sdx will be sdx.log
log = open(log_file, "a+")
record_timestamp(log=log, action="start")
# Create mark on disk
mark_files = ["_".join([name, "mark_data"]),
"_".join([name, "front_end"]),
"_".join([name, "back_end"])]
disk_size = get_disk_size(disk_name, log, mark_files)
back_skip = str(int(disk_size) / 512 - int(FRONT_SKIP))
mark_on_disk(disk_name, log, False, back_skip, mark_files) # Create marks on disk
# Retry 3 times to run secure erase command
# This is a workaround for hdparm/dd tool comfliction
exit_status = {}
for i in range(3):
exit_status = robust_check_call(cmd=cmd, log=log)
if exit_status["exit_code"] not in HDPARM_RETRY_EXITCODES:
break
time.sleep(0.5)
if exit_status["exit_code"] == 0:
mark_on_disk(disk_name, log, True, back_skip, mark_files) # Verify marks on disk
record_timestamp(log=log, action="complete")
log.close()
return exit_status
def hdparm_check_drive_status(pattern, disk_name, log):
"""
Verify drive SE status use "hdparm -I /dev/sdx" command.
:param pattern: re patten to match different SE status
:param disk_name: disk device name
:param log: an opened file object to store logs
"""
command = ["hdparm", "-I", disk_name]
exit_status = robust_check_output(cmd=command, log=log)
assert exit_status["exit_code"] == 0, "Can't get drive %s SE or ESE status" % disk_name
output = exit_status["message"]
secure_index = output.find("Security")
assert secure_index != -1, \
"Can't find security info, probably disk %s doesn't support SE and ESE" % disk_name
output_secure_items = output[secure_index:-1]
assert pattern.match(output_secure_items), "Disk is not enabled for secure erase"
return
def hdparm_secure_erase(disk_name, se_option):
"""
Secure erase using hdparm tool
:param disk_name: disk to be erased
:param se_option: secure erase option
:return: a dict includes SE command exitcode and SE message
"""
# enhance_se = ARG_LIST.e
log_file = disk_name.split("/")[-1] + ".log" # log file for sdx will be sdx.log
log = open(log_file, "a+")
if se_option:
hdparm_option = "--" + se_option
else:
hdparm_option = "--security-erase" # Default is security erase
# Hdparm SE Step1: check disk status
#
# Secure Erase supported output example
# Security:
# Master password revision code = 65534
# supported
# not enabled
# not locked
# not frozen
# not expired: security count
# supported: enhanced erase
# 2min for SECURITY ERASE UNIT. 2min for ENHANCED SECURITY ERASE UNIT.
# Checksum: correct
#
# except for "supported" and "enabled", other items should have "not" before them
if hdparm_option == "--security-erase":
pattern_se_support = re.compile(r'[\s\S]*(?!not)[\s]*supported'
r'[\s]*[\s\S]*enabled[\s]*not[\s]'
r'*locked[\s]*not[\s]*frozen[\s]*not[\s]*expired[\s\S]*')
else:
pattern_se_support = re.compile(r'[\s\S]*(?!not)[\s]*supported[\s]*[\s\S]*enabled[\s]*not'
r'[\s]*locked[\s]*not[\s]*frozen[\s]*not[\s]*expired[\s\S]*'
r'supported: enhanced erase[\s\S]*')
hdparm_check_drive_status(pattern_se_support, disk_name, log)
# TODO: add section to unlocked a disk
# Hdparm SE Step2: set password
command = ["hdparm", "--verbose", "--user-master", "u",
"--security-set-pass", SE_PASSWORD, disk_name]
assert robust_check_call(command, log)["exit_code"] == 0, \
"Failed to set password for disk " + disk_name
# Hdparm SE Step3: confirm disk is ready for secure erase
# both "supported" and "enabled" should have no "not" before them
# other items should still have "not" before them
pattern_se_enabled = re.compile(r'[\s\S]*(?!not)[\s]*supported[\s]*(?!not)[\s]*enabled[\s]*not'
r'[\s]*locked[\s]*not[\s]*frozen[\s]*not[\s]*expired[\s\S]*')
hdparm_check_drive_status(pattern_se_enabled, disk_name, log)
log.close()
# Hdparm SE step4: run secure erase command
command = ["hdparm", "--verbose", "--user-master", "u", hdparm_option, SE_PASSWORD, disk_name]
return secure_erase_base(disk_name, command)
def sg_format_secure_erase(disk_name, se_option):
"""
Secure erase using sg_format tool
:param disk_name: disk to be erased
:return: a dict includes SE command exitcode and SE message
"""
if se_option:
glist_erase_bit = "--cmplst=" + se_option
else:
# default Glist erasing disabled
glist_erase_bit = "--cmplst=1"
command = ["sg_format", "-v", "--format", glist_erase_bit, disk_name]
return secure_erase_base(disk_name, cmd=command)
def sg_sanitize_secure_erase(disk_name, se_option):
"""
Secure erase using sg_sanitize tool
:param disk_name: disk to be erased
:return: a dict includes SE command exitcode and SE message
"""
if se_option:
sanitize_option = "--" + se_option
else:
sanitize_option = "--block" # default use block erasing
command = ["sg_sanitize", "-v", sanitize_option, disk_name]
return secure_erase_base(disk_name, cmd=command)
def scrub_secure_erase(disk_name, se_option):
"""
Secure erase using scrub tool
:param disk_name: disk to be erased
:return: a dict includes SE command exitcode and SE message
"""
if se_option:
scrub_option = se_option
else:
scrub_option = "nnsa" # default use nnsa standard
command = ["scrub", "-f", "-p", scrub_option, disk_name] # -f is to force erase
return secure_erase_base(disk_name, cmd=command)
def delete_logs(disks):
"""
Delete existing log file for given disks before progress monitoring start
:param disks: disks to be erased
:return:
"""
for value in disks:
value = value.split("/")[-1]
log_file = PATH + value + ".log"
if os.path.exists(log_file):
os.remove(log_file)
def get_process_exit_status(async_result):
"""
Get subprocess exit status
:param async_result: multiprocessing Pool async result object
:return: a dict includes process exit code and exit status description
"""
process_result = {}
try:
process_exit_result = async_result.get()
except AssertionError as err:
process_result = {"exitcode": -1, "message": err}
else:
process_result["exitcode"] = process_exit_result["exit_code"]
if process_result["exitcode"] == 0:
process_result["message"] = "Secure erase completed successfully"
else:
process_result["message"] = process_exit_result["message"]
return process_result
def progress_wrapper(obj):
"""
Progress object wrapper, to make it picklable
:param obj: an object of Progress
:return:
"""
return obj.run()
if __name__ == '__main__':
TOOL_MAPPER = {
"scrub": scrub_secure_erase,
"hdparm": hdparm_secure_erase,
"sg_format": sg_format_secure_erase,
"sg_sanitize": sg_sanitize_secure_erase
}
tool = ARG_LIST.t
option = ARG_LIST.o
task_id = ARG_LIST.i
server = ARG_LIST.s
assert tool in ["scrub", "hdparm", "sg_format", "sg_sanitize"], \
"Secure erase tool is not supported"
# Get drive list without RAID
disk_list = set(convert_raid_to_jbod())
delete_logs(disk_list)
# Get process count we should started
# user_count = len(disk_list)
# cpu_thread_count = cpu_count() - 1
# if user_count > cpu_thread_count:
# process_count = cpu_thread_count
# else:
# process_count = user_count
process_count = cpu_count()
#rubicon hard code - ideally this could be passed down as a template option.
process_count = 63
pool = Pool(process_count)
#Get secure erase progress and send notification
progress_parser = Progress(disk_list, PATH,
{"taskId": task_id, "option": option,
"tool": tool, "address": server})
progress_status = pool.apply_async(progress_wrapper, (progress_parser, ))
# Run multiple processes for SE
erase_output_list = []
for disk in disk_list:
erase_output = {"seMethod": tool, "disk": disk}
result = pool.apply_async(TOOL_MAPPER[tool], args=(disk, option))
erase_output["poolExitStatus"] = result
erase_output_list.append(erase_output)
progress_result = get_process_exit_status(progress_status)
# Parse erase exit message
# .get() is a method blocks main process
erase_result_list = []
for erase_output in erase_output_list:
erase_result = {"seMethod": erase_output["seMethod"],
"disk": erase_output["disk"]}
erase_result.update(get_process_exit_status(erase_output["poolExitStatus"]))
erase_result_list.append(erase_result)
pool.close()
pool.join()
if progress_result["exitcode"]:
print progress_result["Message"]
print erase_result_list
for erase_result in erase_result_list:
if erase_result["exitcode"]:
msg = "Drive %s failed to run secure erase with tool %s, error info are: \n %s" \
% (erase_result["disk"], erase_result["seMethod"], erase_result["message"])
sys.exit(msg)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment