一鍵重新掛盤

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import json
import datetime
import logging
import os
import subprocess

logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s %(process)d %(thread)d %(levelname)s %(message)s @%(pathname)s:%(lineno)d')


def custom_subprocess_run(cmd):
    logging.info('execute cmd %s' % cmd)
    return subprocess.check_output(cmd, shell=True)


class DiskTool(object):

    def __init__(self, prefix, skip_disk_with_mount=True):
        self.skip_disk_with_mount = skip_disk_with_mount
        self.prefix = prefix

    @staticmethod
    def ls_blk():
        output = custom_subprocess_run("lsblk --json --output-all")
        blk_info = json.loads(output)
        return blk_info['blockdevices']

    def find_mount_line_by_dev(self, dev):
        mount_points = []
        for disk in self.ls_blk():

            if disk['kname'] == dev and disk.get('mountpoint'):
                mount_points.append(disk['mountpoint'])

            if disk.get('children'):
                for part in disk['children']:
                    if part['kname'].startswith(dev) and part.get('mountpoint'):
                        mount_points.append(part['mountpoint'])

        return mount_points

    def is_dev_need_process(self, dev):
        if dev.get('type') not in ('disk', 'part'):
            # 只處理磁盤和分區(qū), 不處理loop設(shè)備等
            logging.info("dev {} type is {}, no need process".format(dev['kname'], dev['type']))
            return False

        if dev.get('mountpoint') == '/':  # 不處理系統(tǒng)盤
            logging.info("dev {} mount /, no need process".format(dev['kname']))
            return False

        if dev['mountpoint'] and self.skip_disk_with_mount:
            logging.info("dev {} mount {}, no need process".format(dev['kname'], dev['mountpoint']))
            return False

        return True

    def find_disk_to_process(self):
        ret = []
        for disk in self.ls_blk():
            if not self.is_dev_need_process(disk):
                continue

            for part in disk.get('children') or []:
                if not self.is_dev_need_process(part):
                    break
            else:
                ret.append(disk)
        return ret

    @staticmethod
    def get_disk_and_part_device_from_disks(disks):
        ret = []
        for disk in disks:
            ret.append(disk.get('kname'))
            for part in disk.get('children') or []:
                ret.append(part.get('kname'))
        return ret

    @staticmethod
    def format_dev(dev, label="ahaha"):
        try_counter = 20
        while try_counter > 0:
            try:
                try_counter -= 1
                cmd = 'mkfs.ext4 -F -L {} /dev/{}'.format(label, dev)
                return custom_subprocess_run(cmd)
            except Exception as e:
                logging.exception('format error: {}'.format(e))
                raise

    def get_uuids(self, disk_dev=None):
        uuids = []
        for dev in self.ls_blk():
            if not disk_dev or dev['kname'] == disk_dev:
                if dev['uuid']:
                    uuids.append(dev['uuid'])
                if dev.get('children'):
                    for part in dev['children']:
                        uuids.append(part['uuid'])
        return uuids

    def is_fstab_line_valid(self, disk_dev, line):
        line = line.strip()
        items = [item.strip() for item in line.split()]
        if not line:
            return False
        if items[1] == "/":  # / mount reserved
            return True

        if line.startswith('#') or line.find(disk_dev) != -1:
            return False

        dev_uuids = self.get_uuids(disk_dev)
        for uuid in dev_uuids:
            if line.find(uuid) != -1:
                return False

        if items[0].startswith('UUID'):  # clear invalid uuid
            mount_uuid = items[0].split('=')[1]
            blk_uuids = self.get_uuids()
            if mount_uuid not in set(blk_uuids):
                return False

        for point in self.find_mount_line_by_dev(disk_dev):
            if items[1].strip() == point:
                return False

        return True

    def cleanup_fstab(self, disk_dev):
        new_lines = []
        with open('/etc/fstab', 'r') as f:
            lines = f.readlines()
        for line in lines:
            if self.is_fstab_line_valid(disk_dev, line):
                new_lines.append(line)
            else:
                logging.info("we remove fstab line {}".format(line))
        with open('/etc/fstab', 'w') as f:
            f.writelines(new_lines)

    def add_disk_to_fstab(self, disk_dev, mountpoint):
        uuids = self.get_uuids(disk_dev)
        if len(uuids) > 1:
            raise Exception("{} has partition, format failed".format(disk_dev))
        with open('/etc/fstab', 'a') as f:
            line = 'UUID={} {} ext4 nofail,noatime 0 2\n'.format(uuids[0], mountpoint)
            logging.info('adding line to /etc/fstab, content=%s' % line.strip())
            f.write(line)

    def find_next_available_dataxx_mountpoint_from_fstab(self):
        with open('/etc/fstab', 'r') as f:
            fstab = f.read()
        for i in range(0, 100):
            mountpoint = self.prefix + str(i).zfill(2)
            if mountpoint not in fstab:
                logging.info('found available mount point from /etc/fstab mount point=%s' % mountpoint)
                return mountpoint
        return None

    def umount_repartition_format_cleanup_fstab_of_disk(self, disk):
        logging.info('processing disk.kname=%s', disk['kname'])
        self.umount_disk([disk])
        self.format_dev(disk['kname'])
        self.cleanup_fstab(disk['kname'])
        return disk['kname']

    def umount_dev_if_mounted(self, dev):
        mount_points = self.find_mount_line_by_dev(dev)
        logging.info(mount_points)
        if not mount_points:
            return True

        for mount in mount_points:
            cmd = 'umount {}'.format(mount)
            custom_subprocess_run(cmd)

    def umount_disk(self, disks):
        disk_and_disk_parts = self.get_disk_and_part_device_from_disks(disks)
        for dev in disk_and_disk_parts:
            self.umount_dev_if_mounted(dev)

    @staticmethod
    def backup_fstab():
        now_str = datetime.datetime.now().strftime("%y.%m.%d_%H.%M.%S")
        custom_subprocess_run("cp /etc/fstab /etc/fstab.{}.bak".format(now_str))

    def remount_all(self):
        self.backup_fstab()
        data_disks = self.find_disk_to_process()
        for disk in data_disks:
            disk_dev = self.umount_repartition_format_cleanup_fstab_of_disk(disk)
            mountpoint = self.find_next_available_dataxx_mountpoint_from_fstab()
            if not os.path.exists(mountpoint):
                os.makedirs(mountpoint)
            self.add_disk_to_fstab(disk_dev, mountpoint)
        custom_subprocess_run("mount -a")


def main():
    # 如果磁盤已經(jīng)掛載默認(rèn)不作處理, 設(shè)置環(huán)境變量skip_disk_with_mount=yes后執(zhí)行,會(huì)卸載掉已經(jīng)掛載的盤進(jìn)行重新格式化&&掛載: 高危操作
    skip_disk_with_mount = os.environ.get('skip_disk_with_mount', 'no') in ('true', 'yes', 'y', '1')
    # 默認(rèn)掛載點(diǎn)以/data 作為前綴, 掛載成/data00捻艳、/data01勾给、/data99這樣的格式
    prefix = os.environ.get("mount_prefix", "/data")

    try:
        disk_tool = DiskTool(prefix, skip_disk_with_mount)
        disk_tool.remount_all()
    except Exception as e:
        logging.exception("do remount data disk faild: {}".format(str(e)))


if __name__ == '__main__':
    main()

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末崇裁,一起剝皮案震驚了整個(gè)濱河市怕磨,隨后出現(xiàn)的幾起案子习柠,更是在濱河造成了極大的恐慌骚揍,老刑警劉巖碍讨,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異碱鳞,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)踱蛀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門窿给,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人率拒,你說(shuō)我怎么就攤上這事填大。” “怎么了俏橘?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵允华,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我寥掐,道長(zhǎng)靴寂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任召耘,我火速辦了婚禮百炬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘污它。我一直安慰自己剖踊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布衫贬。 她就那樣靜靜地躺著德澈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪固惯。 梳的紋絲不亂的頭發(fā)上梆造,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音葬毫,去河邊找鬼镇辉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛贴捡,可吹牛的內(nèi)容都是我干的忽肛。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼烂斋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼屹逛!你這毒婦竟也來(lái)了础废?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤煎源,失蹤者是張志新(化名)和其女友劉穎色迂,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體手销,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡歇僧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了锋拖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诈悍。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖兽埃,靈堂內(nèi)的尸體忽然破棺而出侥钳,到底是詐尸還是另有隱情,我是刑警寧澤柄错,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布舷夺,位于F島的核電站,受9級(jí)特大地震影響售貌,放射性物質(zhì)發(fā)生泄漏给猾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一颂跨、第九天 我趴在偏房一處隱蔽的房頂上張望敢伸。 院中可真熱鬧,春花似錦恒削、人聲如沸池颈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)躯砰。三九已至,卻和暖如春斑粱,著一層夾襖步出監(jiān)牢的瞬間弃揽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工则北, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人痕慢。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓尚揣,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親掖举。 傳聞我的和親對(duì)象是個(gè)殘疾皇子快骗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容