Add action to zap disk(s)
This action includes configuration for disk(s) to zap, as well as an additional required flag for the administrator to acknowledge pending data loss Change-Id: I3106e2f10cf132a628aad025f73161b04215598e Related-Bug: #1698154
This commit is contained in:
parent
2c3eae272f
commit
487658abe0
25
actions.yaml
25
actions.yaml
|
@ -73,3 +73,28 @@ blacklist-remove-disk:
|
||||||
Example: '/dev/vdb /var/tmp/test-osd'
|
Example: '/dev/vdb /var/tmp/test-osd'
|
||||||
required:
|
required:
|
||||||
- osd-devices
|
- osd-devices
|
||||||
|
zap-disk:
|
||||||
|
description: |
|
||||||
|
Purge disk of all data and signatures for use by Ceph
|
||||||
|
.
|
||||||
|
This action can be necessary in cases where a Ceph cluster is being
|
||||||
|
redeployed as the charm defaults to skipping disks that look like Ceph
|
||||||
|
devices in order to preserve data. In order to forcibly redeploy, the
|
||||||
|
admin is required to perform this action for each disk to be re-consumed.
|
||||||
|
.
|
||||||
|
In addition to triggering this action, it is required to pass an additional
|
||||||
|
parameter option of `i-really-mean-it` to ensure that the
|
||||||
|
administrator is aware that this *will* cause data loss on the specified
|
||||||
|
device(s)
|
||||||
|
params:
|
||||||
|
devices:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
A space-separated list of devices to remove the partition table from.
|
||||||
|
i-really-mean-it:
|
||||||
|
type: boolean
|
||||||
|
description: |
|
||||||
|
This must be toggled to enable actually performing this action
|
||||||
|
required:
|
||||||
|
- devices
|
||||||
|
- i-really-mean-it
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
zap_disk.py
|
|
@ -0,0 +1,91 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# Copyright 2018 Canonical Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.append('lib')
|
||||||
|
sys.path.append('hooks')
|
||||||
|
|
||||||
|
import charmhelpers.core.hookenv as hookenv
|
||||||
|
from charmhelpers.contrib.storage.linux.utils import (
|
||||||
|
is_block_device,
|
||||||
|
is_device_mounted,
|
||||||
|
zap_disk,
|
||||||
|
)
|
||||||
|
from charmhelpers.core.unitdata import kv
|
||||||
|
from ceph.utils import is_active_bluestore_device
|
||||||
|
|
||||||
|
|
||||||
|
def get_devices():
|
||||||
|
"""Parse 'devices' action parameter, returns list."""
|
||||||
|
devices = []
|
||||||
|
for path in hookenv.action_get('devices').split(' '):
|
||||||
|
path = path.strip()
|
||||||
|
if not os.path.isabs(path):
|
||||||
|
hookenv.action_fail('{}: Not absolute path.'.format(path))
|
||||||
|
raise
|
||||||
|
devices.append(path)
|
||||||
|
return devices
|
||||||
|
|
||||||
|
|
||||||
|
def zap():
|
||||||
|
if not hookenv.action_get('i-really-mean-it'):
|
||||||
|
hookenv.action_fail('i-really-mean-it is a required parameter')
|
||||||
|
return
|
||||||
|
|
||||||
|
failed_devices = []
|
||||||
|
not_block_devices = []
|
||||||
|
devices = get_devices()
|
||||||
|
for device in devices:
|
||||||
|
if not is_block_device(device):
|
||||||
|
not_block_devices.append(device)
|
||||||
|
if is_device_mounted(device) or is_active_bluestore_device(device):
|
||||||
|
failed_devices.append(device)
|
||||||
|
|
||||||
|
if failed_devices or not_block_devices:
|
||||||
|
message = ""
|
||||||
|
if failed_devices:
|
||||||
|
message = "{} devices are mounted: {}".format(
|
||||||
|
len(failed_devices),
|
||||||
|
", ".join(failed_devices))
|
||||||
|
if not_block_devices:
|
||||||
|
if message is not '':
|
||||||
|
message += "\n\n"
|
||||||
|
message += "{} devices are not block devices: {}".format(
|
||||||
|
len(not_block_devices),
|
||||||
|
", ".join(not_block_devices))
|
||||||
|
hookenv.action_fail(message)
|
||||||
|
return
|
||||||
|
db = kv()
|
||||||
|
used_devices = db.get('osd-devices', [])
|
||||||
|
for device in devices:
|
||||||
|
zap_disk(device)
|
||||||
|
if device in used_devices:
|
||||||
|
used_devices.remove(device)
|
||||||
|
db.set('osd-devices', used_devices)
|
||||||
|
db.flush()
|
||||||
|
hookenv.action_set({
|
||||||
|
'message': "{} disk(s) have been zapped, to use them as OSDs, run: \n"
|
||||||
|
"juju run-action {} add-disk osd-devices=\"{}\"".format(
|
||||||
|
len(devices),
|
||||||
|
hookenv.local_unit(),
|
||||||
|
" ".join(devices))
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
zap()
|
|
@ -0,0 +1,129 @@
|
||||||
|
# Copyright 2018 Canonical Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from actions import zap_disk
|
||||||
|
|
||||||
|
from test_utils import CharmTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class ZapDiskActionTests(CharmTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(ZapDiskActionTests, self).setUp(
|
||||||
|
zap_disk, ['hookenv',
|
||||||
|
'is_block_device',
|
||||||
|
'is_device_mounted',
|
||||||
|
'is_active_bluestore_device',
|
||||||
|
'kv'])
|
||||||
|
self.is_device_mounted.return_value = False
|
||||||
|
self.is_block_device.return_value = True
|
||||||
|
self.is_active_bluestore_device.return_value = False
|
||||||
|
self.kv.return_value = self.kv
|
||||||
|
self.hookenv.local_unit.return_value = "ceph-osd-test/0"
|
||||||
|
|
||||||
|
@mock.patch.object(zap_disk, 'zap_disk')
|
||||||
|
def test_authorized_zap_single_disk(self,
|
||||||
|
_zap_disk):
|
||||||
|
"""Will zap disk with extra config set"""
|
||||||
|
def side_effect(arg):
|
||||||
|
return {
|
||||||
|
'devices': '/dev/vdb',
|
||||||
|
'i-really-mean-it': True,
|
||||||
|
}.get(arg)
|
||||||
|
self.hookenv.action_get.side_effect = side_effect
|
||||||
|
self.kv.get.return_value = ['/dev/vdb', '/dev/vdz']
|
||||||
|
zap_disk.zap()
|
||||||
|
_zap_disk.assert_called_with('/dev/vdb')
|
||||||
|
self.kv.get.assert_called_with('osd-devices', [])
|
||||||
|
self.kv.set.assert_called_with('osd-devices', ['/dev/vdz'])
|
||||||
|
self.hookenv.action_set.assert_called_with({
|
||||||
|
'message': "1 disk(s) have been zapped, to use "
|
||||||
|
"them as OSDs, run: \njuju "
|
||||||
|
"run-action ceph-osd-test/0 add-disk "
|
||||||
|
"osd-devices=\"/dev/vdb\""
|
||||||
|
})
|
||||||
|
|
||||||
|
@mock.patch.object(zap_disk, 'zap_disk')
|
||||||
|
def test_authorized_zap_multiple_disks(self,
|
||||||
|
_zap_disk):
|
||||||
|
"""Will zap disk with extra config set"""
|
||||||
|
def side_effect(arg):
|
||||||
|
return {
|
||||||
|
'devices': '/dev/vdb /dev/vdc',
|
||||||
|
'i-really-mean-it': True,
|
||||||
|
}.get(arg)
|
||||||
|
self.hookenv.action_get.side_effect = side_effect
|
||||||
|
self.kv.get.return_value = ['/dev/vdb', '/dev/vdz']
|
||||||
|
zap_disk.zap()
|
||||||
|
_zap_disk.assert_has_calls([
|
||||||
|
mock.call('/dev/vdb'),
|
||||||
|
mock.call('/dev/vdc'),
|
||||||
|
])
|
||||||
|
self.kv.get.assert_called_with('osd-devices', [])
|
||||||
|
self.kv.set.assert_called_with('osd-devices', ['/dev/vdz'])
|
||||||
|
self.hookenv.action_set.assert_called_with({
|
||||||
|
'message': "2 disk(s) have been zapped, to use "
|
||||||
|
"them as OSDs, run: \njuju "
|
||||||
|
"run-action ceph-osd-test/0 add-disk "
|
||||||
|
"osd-devices=\"/dev/vdb /dev/vdc\""
|
||||||
|
})
|
||||||
|
|
||||||
|
@mock.patch.object(zap_disk, 'zap_disk')
|
||||||
|
def test_wont_zap_non_block_device(self,
|
||||||
|
_zap_disk,):
|
||||||
|
"""Will not zap a disk that isn't a block device"""
|
||||||
|
def side_effect(arg):
|
||||||
|
return {
|
||||||
|
'devices': '/dev/vdb',
|
||||||
|
'i-really-mean-it': True,
|
||||||
|
}.get(arg)
|
||||||
|
self.hookenv.action_get.side_effect = side_effect
|
||||||
|
self.is_block_device.return_value = False
|
||||||
|
zap_disk.zap()
|
||||||
|
_zap_disk.assert_not_called()
|
||||||
|
self.hookenv.action_fail.assert_called_with(
|
||||||
|
"1 devices are not block devices: /dev/vdb")
|
||||||
|
|
||||||
|
@mock.patch.object(zap_disk, 'zap_disk')
|
||||||
|
def test_wont_zap_mounted_block_device(self,
|
||||||
|
_zap_disk):
|
||||||
|
"""Will not zap a disk that is mounted"""
|
||||||
|
def side_effect(arg):
|
||||||
|
return {
|
||||||
|
'devices': '/dev/vdb',
|
||||||
|
'i-really-mean-it': True,
|
||||||
|
}.get(arg)
|
||||||
|
self.hookenv.action_get.side_effect = side_effect
|
||||||
|
self.is_device_mounted.return_value = True
|
||||||
|
zap_disk.zap()
|
||||||
|
_zap_disk.assert_not_called()
|
||||||
|
self.hookenv.action_fail.assert_called_with(
|
||||||
|
"1 devices are mounted: /dev/vdb")
|
||||||
|
|
||||||
|
@mock.patch.object(zap_disk, 'zap_disk')
|
||||||
|
def test_wont_zap__mounted_bluestore_device(self,
|
||||||
|
_zap_disk):
|
||||||
|
"""Will not zap a disk that is mounted"""
|
||||||
|
def side_effect(arg):
|
||||||
|
return {
|
||||||
|
'devices': '/dev/vdb',
|
||||||
|
'i-really-mean-it': True,
|
||||||
|
}.get(arg)
|
||||||
|
self.hookenv.action_get.side_effect = side_effect
|
||||||
|
self.is_active_bluestore_device.return_value = True
|
||||||
|
zap_disk.zap()
|
||||||
|
_zap_disk.assert_not_called()
|
||||||
|
self.hookenv.action_fail.assert_called_with(
|
||||||
|
"1 devices are mounted: /dev/vdb")
|
Loading…
Reference in New Issue