Merge pull request #5452 from leseb/osd-wipe

ceph: enhancement disk sanitize options
This commit is contained in:
Sébastien Han authored and GitHub committed 2020-07-22 16:11:35 +02:00
commit 8f4e37b80a
16 files changed
+310 -259

No files matched your search

+27 -24
View File
@@ -149,30 +149,7 @@ For more details on the mons and when to choose a number other than `3`, see the
* `manageMachineDisruptionBudgets`: if `true`, the operator will create and manage MachineDisruptionBudgets to ensure OSDs are only fenced when the cluster is healthy. Only available on OpenShift.
* `machineDisruptionBudgetNamespace`: the namespace in which to watch the MachineDisruptionBudgets.
* `removeOSDsIfOutAndSafeToRemove`: If `true` the operator will remove the OSDs that are down and whose data has been restored to other OSDs. In Ceph terms, the osds are `out` and `safe-to-destroy` when then would be removed.
* `cleanupPolicy`: The section for confirming that cluster data should be forcibly deleted. The cleanupPolicy should only be added to the cluster when the cluster is about to be deleted. After any field of the cleanup policy is set, Rook will stop configuring the cluster as if the cluster is about to be destroyed in order to prevent these settings from being deployed unintentionally.
* `confirmation`: If `yes-really-destroy-data` the operator will automatically delete data on the hostpath of cluster nodes and clean devices with OSDs when a `delete cephcluster` command is issued. Only `yes-really-destroy-data` and an empty string are valid values for this field.
* `healthCheck`: control period health status checks and livenessprobes, see the [health settings](#health-settings)
To activate the cleanup, you can use the following command **AT YOUR OWN RISK**:
```console
kubectl -n rook-ceph patch cephcluster rook-ceph --type merge -p '{"spec":{"cleanupPolicy":{"confirmation":"yes-really-destroy-data"}}}'
```
When applied you will see the following from the Operator's logs:
```text
2020-05-27 13:24:04.267665 I | ceph-spec: CR has changed for "rook-ceph". diff= v1.ClusterSpec{
... // 16 identical fields
Mgr: v1.MgrSpec{},
RemoveOSDsIfOutAndSafeToRemove: false,
- CleanupPolicy: v1.CleanupPolicySpec{},
+ CleanupPolicy: v1.CleanupPolicySpec{Confirmation: "yes-really-destroy-data"},
}
```
Nothing will happen until the deletion of the CR is requested, so this can still be reverted.
However, all new orchestration/reconciliation will be blocked with this cleanup policy enabled.
* `cleanupPolicy`: [cleanup policy settings](#cleanup-policy)
### Ceph container images
@@ -1029,3 +1006,29 @@ spec:
cephVersion:
image: ceph/ceph:v15.2.4 # Should match external cluster version
```
### Cleanup policy
Rook has the ability to cleanup resources and data that were deployed.
The policy represents the confirmation that cluster data should be forcibly deleted.
The cleanupPolicy should only be added to the cluster when the cluster is about to be deleted.
After the `confirmation` field of the cleanup policy is set, Rook will stop configuring the cluster as if the cluster is about to be destroyed in order to prevent these settings from being deployed unintentionally.
The `cleanupPolicy` CR settings has different fields:
* `confirmation`: If `yes-really-destroy-data` the operator will automatically delete data on the hostpath of cluster nodes and clean devices with OSDs when a `delete cephcluster` command is issued. Only `yes-really-destroy-data` and an empty string are valid values for this field.
* `sanitizeDisks`: sanitizeDisks represents advanced settings that can be used to sanitize drives.
By default if the `confirmation` is set, Rook will only remove Ceph's metadata, which allows a cluster re-installation if desired.
However, the administrator might want to sanitize the drives in more depth with the following flags:
* `method`: indicates if the entire disk should be sanitized or simply ceph's metadata. Possible choices are 'quick' (default) or 'complete'
* `dataSource`: indicate where to get random bytes from to write on the disk. Possible choices are 'zero' (default) or 'random'.
Using random sources will consume entropy from the system and will take much more time then the zero source
* `iteration`: overwrite N times instead of the default (1). Takes an integer value
To automate activation of the cleanup, you can use the following command. **WARNING: DATA WILL BE PERMANENTLY DELETED**:
```console
kubectl -n rook-ceph patch cephcluster rook-ceph --type merge -p '{"spec":{"cleanupPolicy":{"confirmation":"yes-really-destroy-data"}}}'
```
Nothing will happen until the deletion of the CR is requested, so this can still be reverted.
However, all new configuration by the operator will be blocked with this cleanup policy enabled.
@@ -169,6 +169,17 @@ spec:
confirmation:
type: string
pattern: ^$|^yes-really-destroy-data$
sanitizeDisks:
properties:
method:
type: string
pattern: ^(complete|quick)$
dataSource:
type: string
pattern: ^(zero|random)$
iteration:
type: integer
format: int32
additionalPrinterColumns:
- name: DataDirHostPath
type: string
+13 -1
View File
@@ -92,7 +92,19 @@ spec:
# To signify that automatic deletion is desired, use the value "yes-really-destroy-data". Only this and an empty
# string are valid values for this field.
confirmation: ""
# sanitizeDisks represents settings for sanitizing OSD disks on cluster deletion
sanitizeDisks:
# method indicates if the entire disk should be sanitized or simply ceph's metadata
# in both case, re-install is possible
# possible choices are 'complete' or 'quick' (default)
method: quick
# dataSource indicate where to get random bytes from to write on the disk
# possible choices are 'zero' (default) or 'random'
# using random sources will consume entropy from the system and will take much more time then the zero source
dataSource: zero
# iteration overwrite N times instead of the default (1)
# takes an integer value
iteration: 1
# To control where various services will be scheduled by kubernetes, use the placement configuration sections below.
# The example under 'all' would have all services scheduled on kubernetes nodes labeled with 'role=storage-node' and
# tolerate taints with a key of 'storage-node'.
@@ -192,6 +192,17 @@ spec:
confirmation:
type: string
pattern: ^$|^yes-really-destroy-data$
sanitizeDisks:
properties:
method:
type: string
pattern: ^(complete|quick)$
dataSource:
type: string
pattern: ^(zero|random)$
iteration:
type: integer
format: int32
placement: {}
resources: {}
healthCheck: {}
+19 -5
View File
@@ -20,6 +20,7 @@ import (
"os"
"github.com/rook/rook/cmd/rook/rook"
cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
cleanup "github.com/rook/rook/pkg/daemon/ceph/cleanup"
"github.com/rook/rook/pkg/daemon/ceph/client"
"github.com/rook/rook/pkg/operator/k8sutil"
@@ -28,10 +29,13 @@ import (
)
var (
dataDirHostPath string
namespaceDir string
monSecret string
clusterFSID string
dataDirHostPath string
namespaceDir string
monSecret string
clusterFSID string
sanitizeMethod string
sanitizeDataSource string
sanitizeIteration int32
)
var cleanUpCmd = &cobra.Command{
@@ -44,6 +48,9 @@ func init() {
cleanUpCmd.Flags().StringVar(&namespaceDir, "namespace-dir", "", "dataDirHostPath on the node")
cleanUpCmd.Flags().StringVar(&monSecret, "mon-secret", "", "monitor secret from the keyring")
cleanUpCmd.Flags().StringVar(&clusterFSID, "cluster-fsid", "", "ceph cluster fsid")
cleanUpCmd.Flags().StringVar(&sanitizeMethod, "sanitize-method", string(cephv1.SanitizeMethodQuick), "sanitize method to use (metadata or data)")
cleanUpCmd.Flags().StringVar(&sanitizeDataSource, "sanitize-data-source", string(cephv1.SanitizeDataSourceZero), "data source to sanitize the disk (zero or random)")
cleanUpCmd.Flags().Int32Var(&sanitizeIteration, "sanitize-iteration", 1, "overwrite N times the disk")
flags.SetFlagsFromEnv(cleanUpCmd.Flags(), rook.RookEnvVarPrefix)
cleanUpCmd.RunE = startCleanUp
}
@@ -64,7 +71,14 @@ func startCleanUp(cmd *cobra.Command, args []string) error {
clusterInfo.FSID = clusterFSID
// Build Sanitizer
s := cleanup.NewDiskSanitizer(createContext(), clusterInfo)
s := cleanup.NewDiskSanitizer(createContext(),
clusterInfo,
&cephv1.SanitizeDisksSpec{
Method: cephv1.SanitizeMethodProperty(sanitizeMethod),
DataSource: cephv1.SanitizeDataSourceProperty(sanitizeDataSource),
Iteration: sanitizeIteration,
},
)
// Start OSD wipe process
s.StartSanitizeDisks()
+17 -1
View File
@@ -17,6 +17,18 @@ limitations under the License.
package v1
const (
// SanitizeDataSourceZero uses /dev/zero as sanitize source
SanitizeDataSourceZero SanitizeDataSourceProperty = "zero"
// SanitizeDataSourceRandom uses `shred's default entropy source
SanitizeDataSourceRandom SanitizeDataSourceProperty = "random"
// SanitizeMethodComplete will sanitize everything on the disk
SanitizeMethodComplete SanitizeMethodProperty = "complete"
// SanitizeMethodQuick will sanitize metdata only on the disk
SanitizeMethodQuick SanitizeMethodProperty = "quick"
// DeleteDataDirOnHostsConfirmation represents the validation to destry dataDirHostPath
DeleteDataDirOnHostsConfirmation CleanupConfirmationProperty = "yes-really-destroy-data"
)
@@ -26,6 +38,10 @@ func (c *CleanupPolicySpec) HasDataDirCleanPolicy() bool {
return c.Confirmation == DeleteDataDirOnHostsConfirmation
}
func (c *CleanupConfirmationProperty) String() string {
func (c *SanitizeMethodProperty) String() string {
return string(*c)
}
func (c *SanitizeDataSourceProperty) String() string {
return string(*c)
}
+12 -1
View File
@@ -716,11 +716,22 @@ type ClientSpec struct {
}
type CleanupPolicySpec struct {
Confirmation CleanupConfirmationProperty `json:"confirmation,omitempty"`
Confirmation CleanupConfirmationProperty `json:"confirmation,omitempty"`
SanitizeDisks SanitizeDisksSpec `json:"sanitizeDisks"`
}
type CleanupConfirmationProperty string
type SanitizeDataSourceProperty string
type SanitizeMethodProperty string
type SanitizeDisksSpec struct {
Method SanitizeMethodProperty `json:"method,omitempty"`
DataSource SanitizeDataSourceProperty `json:"dataSource,omitempty"`
Iteration int32 `json:"iteration,omitempty"`
}
// +genclient
// +genclient:noStatus
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+38 -19
View File
@@ -23,6 +23,7 @@ import (
"sync"
"github.com/coreos/pkg/capnslog"
cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
"github.com/rook/rook/pkg/clusterd"
"github.com/rook/rook/pkg/daemon/ceph/client"
"github.com/rook/rook/pkg/daemon/ceph/osd"
@@ -30,11 +31,8 @@ import (
)
const (
ddBS = "1M" // DD's block size
ddCount = "10" // DD runs over the first 10 offsets
ddFlags = "direct,dsync" // DD's sync flags"
ddIf = "/dev/zero"
ddUtility = "dd"
shredUtility = "shred"
shredBS = "10M" // Shred's block size
)
var (
@@ -43,15 +41,17 @@ var (
// DiskSanitizer is simple struct to old the context to execute the commands
type DiskSanitizer struct {
context *clusterd.Context
clusterInfo *client.ClusterInfo
context *clusterd.Context
clusterInfo *client.ClusterInfo
sanitizeDisksSpec *cephv1.SanitizeDisksSpec
}
// NewDiskSanitizer is function that returns a full filled DiskSanitizer object
func NewDiskSanitizer(context *clusterd.Context, clusterInfo *client.ClusterInfo) *DiskSanitizer {
func NewDiskSanitizer(context *clusterd.Context, clusterInfo *client.ClusterInfo, sanitizeDisksSpec *cephv1.SanitizeDisksSpec) *DiskSanitizer {
return &DiskSanitizer{
context: context,
clusterInfo: clusterInfo,
context: context,
clusterInfo: clusterInfo,
sanitizeDisksSpec: sanitizeDisksSpec,
}
}
@@ -144,23 +144,42 @@ func (s *DiskSanitizer) returnPVDevice(disk string) []string {
return strings.Split(output, ":")
}
func (s *DiskSanitizer) buildDDArgs(disk string) []string {
ddArgs := []string{
fmt.Sprintf("if=%s", ddIf),
fmt.Sprintf("of=%s", disk),
fmt.Sprintf("bs=%s", ddBS),
fmt.Sprintf("count=%s", ddCount),
fmt.Sprintf("oflag=%s", ddFlags),
func (s *DiskSanitizer) buildDataSource() string {
return fmt.Sprintf("/dev/%s", s.sanitizeDisksSpec.DataSource.String())
}
func (s *DiskSanitizer) buildShredArgs(disk string) []string {
var shredArgs []string
// If data source is not zero, then let's add zeros at the end of the pass
if s.sanitizeDisksSpec.DataSource != cephv1.SanitizeDataSourceZero {
shredArgs = append(shredArgs, "--zero")
}
return ddArgs
// If this is a quick pass let's just overwrite the first 10MB
if s.sanitizeDisksSpec.Method == cephv1.SanitizeMethodQuick {
shredArgs = append(shredArgs, fmt.Sprintf("--size=%s", shredBS))
}
// If the data source for randomness is zero
if s.sanitizeDisksSpec.DataSource == cephv1.SanitizeDataSourceZero {
shredArgs = append(shredArgs, fmt.Sprintf("--random-source=%s", s.buildDataSource()))
}
shredArgs = append(shredArgs, []string{
"--force",
"--verbose",
fmt.Sprintf("--iterations=%s", strconv.Itoa(int(s.sanitizeDisksSpec.Iteration))),
disk}...)
return shredArgs
}
func (s *DiskSanitizer) executeSanitizeCommand(disk string, wg *sync.WaitGroup) {
// On return, notify the WaitGroup that we’re done
defer wg.Done()
output, err := s.context.Executor.ExecuteCommandWithCombinedOutput(ddUtility, s.buildDDArgs(disk)...)
output, err := s.context.Executor.ExecuteCommandWithCombinedOutput(shredUtility, s.buildShredArgs(disk)...)
if err != nil {
logger.Errorf("failed to sanitize osd disk %q. %s. %v", disk, output, err)
}
+68
View File
@@ -0,0 +1,68 @@
/*
Copyright 2020 The Rook Authors. All rights reserved.
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.
*/
package cleanup
import (
"reflect"
"testing"
cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
"github.com/rook/rook/pkg/clusterd"
"github.com/rook/rook/pkg/daemon/ceph/client"
"github.com/stretchr/testify/assert"
)
func TestBuildDataSource(t *testing.T) {
s := NewDiskSanitizer(&clusterd.Context{}, &client.ClusterInfo{}, &cephv1.SanitizeDisksSpec{})
s.sanitizeDisksSpec.DataSource = cephv1.SanitizeDataSourceZero
assert.Equal(t, "/dev/zero", s.buildDataSource())
}
func TestBuildShredArgs(t *testing.T) {
var i int32 = 1
c := &clusterd.Context{}
disk := "/dev/sda"
type fields struct {
context *clusterd.Context
clusterInfo *client.ClusterInfo
sanitizeDisksSpec *cephv1.SanitizeDisksSpec
}
tests := []struct {
name string
fields fields
disk string
want []string
}{
{"quick-zero", fields{c, &client.ClusterInfo{}, &cephv1.SanitizeDisksSpec{Method: cephv1.SanitizeMethodQuick, Iteration: i, DataSource: cephv1.SanitizeDataSourceZero}}, disk, []string{"--size=10M", "--random-source=/dev/zero", "--force", "--verbose", "--iterations=1", "/dev/sda"}},
{"quick-random", fields{c, &client.ClusterInfo{}, &cephv1.SanitizeDisksSpec{Method: cephv1.SanitizeMethodQuick, Iteration: i, DataSource: cephv1.SanitizeDataSourceRandom}}, disk, []string{"--zero", "--size=10M", "--force", "--verbose", "--iterations=1", "/dev/sda"}},
{"complete-zero", fields{c, &client.ClusterInfo{}, &cephv1.SanitizeDisksSpec{Method: cephv1.SanitizeMethodComplete, Iteration: i, DataSource: cephv1.SanitizeDataSourceZero}}, disk, []string{"--random-source=/dev/zero", "--force", "--verbose", "--iterations=1", "/dev/sda"}},
{"complete-random", fields{c, &client.ClusterInfo{}, &cephv1.SanitizeDisksSpec{Method: cephv1.SanitizeMethodComplete, Iteration: i, DataSource: cephv1.SanitizeDataSourceRandom}}, disk, []string{"--zero", "--force", "--verbose", "--iterations=1", "/dev/sda"}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &DiskSanitizer{
context: tt.fields.context,
clusterInfo: tt.fields.clusterInfo,
sanitizeDisksSpec: tt.fields.sanitizeDisksSpec,
}
if got := s.buildShredArgs(tt.disk); !reflect.DeepEqual(got, tt.want) {
t.Errorf("DiskSanitizer.buildShredArgs() = %v, want %v", got, tt.want)
}
})
}
}
+1 -1
View File
@@ -55,7 +55,7 @@ func StartOSD(context *clusterd.Context, osdType, osdID, osdUUID, lvPath string,
}
// Update LVM config at runtime
if err := updateLVMConfig(context, pvcBackedOSD, lvBackedPV); err != nil {
if err := UpdateLVMConfig(context, pvcBackedOSD, lvBackedPV); err != nil {
return errors.Wrap(err, "failed to update lvm configuration file") // fail return here as validation provided by ceph-volume
}
+3 -2
View File
@@ -179,7 +179,7 @@ func (a *OsdAgent) configureCVDevices(context *clusterd.Context, devices *Device
//
// Or keep doing this if the PV is backend by an LV already
if !a.clusterInfo.CephVersion.IsAtLeast(cephVolumeRawModeMinCephVersion) || lvBackedPV {
if err := updateLVMConfig(context, a.pvcBacked, lvBackedPV); err != nil {
if err := UpdateLVMConfig(context, a.pvcBacked, lvBackedPV); err != nil {
return nil, errors.Wrap(err, "failed to update lvm configuration file")
}
}
@@ -343,7 +343,8 @@ func getLVPath(op string) string {
return ""
}
func updateLVMConfig(context *clusterd.Context, onPVC, lvBackedPV bool) error {
// UpdateLVMConfig updates the lvm.conf file
func UpdateLVMConfig(context *clusterd.Context, onPVC, lvBackedPV bool) error {
input, err := ioutil.ReadFile(lvmConfPath)
if err != nil {
+18 -5
View File
@@ -18,6 +18,7 @@ package cluster
import (
"fmt"
"strconv"
"time"
"github.com/pkg/errors"
@@ -44,11 +45,16 @@ const (
)
var (
volumeName = "cleanup-volume"
dataDirHostPath = "ROOK_DATA_DIR_HOST_PATH"
namespaceDir = "ROOK_NAMESPACE_DIR"
monitorSecret = "ROOK_MON_SECRET"
clusterFSID = "ROOK_CLUSTER_FSID"
volumeName = "cleanup-volume"
dataDirHostPath = "ROOK_DATA_DIR_HOST_PATH"
namespaceDir = "ROOK_NAMESPACE_DIR"
monitorSecret = "ROOK_MON_SECRET"
clusterFSID = "ROOK_CLUSTER_FSID"
sanitizeMethod = "ROOK_SANITIZE_METHOD"
sanitizeDataSource = "ROOK_SANITIZE_DATA_SOURCE"
sanitizeLayer = "ROOK_SANITIZE_LAYER"
sanitizeIteration = "ROOK_SANITIZE_ITERATION"
sanitizeIterationDefault int32 = 1
)
func (c *ClusterController) startClusterCleanUp(stopCleanupCh chan struct{}, cluster *cephv1.CephCluster, cephHosts []string, monSecret, clusterFSID string) {
@@ -94,6 +100,10 @@ func (c *ClusterController) cleanUpJobContainer(cluster *cephv1.CephCluster, mon
volumeMounts := []v1.VolumeMount{}
envVars := []v1.EnvVar{}
if cluster.Spec.DataDirHostPath != "" {
if cluster.Spec.CleanupPolicy.SanitizeDisks.Iteration == 0 {
cluster.Spec.CleanupPolicy.SanitizeDisks.Iteration = sanitizeIterationDefault
}
hostPathVolumeMount := v1.VolumeMount{Name: volumeName, MountPath: cluster.Spec.DataDirHostPath}
devMount := v1.VolumeMount{Name: "devices", MountPath: "/dev"}
volumeMounts = append(volumeMounts, hostPathVolumeMount)
@@ -105,6 +115,9 @@ func (c *ClusterController) cleanUpJobContainer(cluster *cephv1.CephCluster, mon
{Name: clusterFSID, Value: cephFSID},
{Name: "ROOK_LOG_LEVEL", Value: "DEBUG"},
mon.PodNamespaceEnvVar(cluster.Namespace),
{Name: sanitizeMethod, Value: cluster.Spec.CleanupPolicy.SanitizeDisks.Method.String()},
{Name: sanitizeDataSource, Value: cluster.Spec.CleanupPolicy.SanitizeDisks.DataSource.String()},
{Name: sanitizeIteration, Value: strconv.Itoa(int(cluster.Spec.CleanupPolicy.SanitizeDisks.Iteration))},
}...)
}
+38 -32
View File
@@ -75,13 +75,14 @@ var controllerTypeMeta = metav1.TypeMeta{
// ReconcileCephObjectStore reconciles a cephObjectStore object
type ReconcileCephObjectStore struct {
client client.Client
bktclient bktclient.Interface
scheme *runtime.Scheme
context *clusterd.Context
cephClusterSpec *cephv1.ClusterSpec
clusterInfo *cephclient.ClusterInfo
objectStoreChannels map[string]*objectStoreHealth
client client.Client
bktclient bktclient.Interface
scheme *runtime.Scheme
context *clusterd.Context
cephClusterSpec *cephv1.ClusterSpec
clusterInfo *cephclient.ClusterInfo
objectStoreChannels map[string]*objectStoreHealth
clusterResourceDeleted bool
}
type objectStoreHealth struct {
@@ -221,35 +222,40 @@ func (r *ReconcileCephObjectStore) reconcile(request reconcile.Request) (reconci
if !cephObjectStore.GetDeletionTimestamp().IsZero() {
logger.Debugf("deleting store %q", cephObjectStore.Name)
response, okToDelete := r.verifyObjectBucketCleanup(cephObjectStore)
if !okToDelete {
// If the object store cannot be deleted, requeue the request for deletion to see if the conditions
// will eventually be satisfied such as the object buckets being removed
return response, nil
}
if !r.clusterResourceDeleted {
response, okToDelete := r.verifyObjectBucketCleanup(cephObjectStore)
if !okToDelete {
// If the object store cannot be deleted, requeue the request for deletion to see if the conditions
// will eventually be satisfied such as the object buckets being removed
return response, nil
}
response, okToDelete = r.verifyObjectUserCleanup(cephObjectStore)
if !okToDelete {
// If the object store cannot be deleted, requeue the request for deletion to see if the conditions
// will eventually be satisfied such as the object users being removed
return response, nil
}
response, okToDelete = r.verifyObjectUserCleanup(cephObjectStore)
if !okToDelete {
// If the object store cannot be deleted, requeue the request for deletion to see if the conditions
// will eventually be satisfied such as the object users being removed
return response, nil
}
// Close the channel to stop the healthcheck of the endpoint
close(r.objectStoreChannels[cephObjectStore.Name].stopChan)
// Close the channel to stop the healthcheck of the endpoint
close(r.objectStoreChannels[cephObjectStore.Name].stopChan)
// Remove object store from the map
delete(r.objectStoreChannels, cephObjectStore.Name)
// Remove object store from the map
delete(r.objectStoreChannels, cephObjectStore.Name)
cfg := clusterConfig{
context: r.context,
store: cephObjectStore,
clusterSpec: r.cephClusterSpec,
clusterInfo: r.clusterInfo,
}
err = cfg.deleteStore()
if err != nil {
return reconcile.Result{}, errors.Wrapf(err, "failed to delete store %q", cephObjectStore.Name)
cfg := clusterConfig{
context: r.context,
store: cephObjectStore,
clusterSpec: r.cephClusterSpec,
clusterInfo: r.clusterInfo,
}
err = cfg.deleteStore()
if err != nil {
return reconcile.Result{}, errors.Wrapf(err, "failed to delete store %q", cephObjectStore.Name)
}
// Mark the ressource deletion as complete
r.clusterResourceDeleted = true
}
// Remove finalizer
+22 -167
View File
@@ -97,10 +97,6 @@ func (h *CephInstaller) CreateCephOperator(namespace string) (err error) {
if err != nil {
return fmt.Errorf("Failed to start admission controllers: %v", err)
}
if err = h.WipeDisks(namespace, 2); err != nil {
return err
}
rookOperator := h.Manifests.GetRookOperator(namespace)
_, err = h.k8shelper.KubectlWithStdin(rookOperator, createFromStdinArgs...)
if err != nil {
@@ -190,9 +186,6 @@ func (h *CephInstaller) CreateRookOperatorViaHelm(namespace, chartSettings strin
}
if err = h.WipeDisks(namespace, 2); err != nil {
return err
}
return nil
}
@@ -527,8 +520,11 @@ func (h *CephInstaller) UninstallRookFromMultipleNS(systemNamespace string, name
for clusterNum, namespace := range namespaces {
if h.cleanupHost {
//Add cleanup policy to the ceph cluster
err = h.addCleanupPolicy(namespace)
assert.NoError(h.T(), err)
// Add cleanup policy to the ceph cluster but NOT the external one
if clusterNum == 0 {
err = h.addCleanupPolicy(namespace)
assert.NoError(h.T(), err)
}
}
if !h.T().Failed() {
@@ -550,17 +546,20 @@ func (h *CephInstaller) UninstallRookFromMultipleNS(systemNamespace string, name
h.GatherAllRookLogs(h.T().Name()+"poolcheck", systemNamespace)
}
roles := h.Manifests.GetClusterRoles(namespace, systemNamespace)
_, err = h.k8shelper.KubectlWithStdin(roles, deleteFromStdinArgs...)
err = h.k8shelper.DeleteResourceAndWait(false, "-n", namespace, "cephcluster", h.clusterName)
checkError(h.T(), err, fmt.Sprintf("cannot remove cluster %s", namespace))
if h.cleanupHost {
err = h.waitForCleanupJobs(namespace)
assert.NoError(h.T(), err)
// The second cluster is external so the cleanup pod will never exist!
if clusterNum == 0 {
err = h.waitForCleanupJobs(namespace)
assert.NoError(h.T(), err)
}
}
roles := h.Manifests.GetClusterRoles(namespace, systemNamespace)
_, err = h.k8shelper.KubectlWithStdin(roles, deleteFromStdinArgs...)
crdCheckerFunc := func() error {
_, err := h.k8shelper.RookClientset.CephV1().CephClusters(namespace).Get(h.clusterName, metav1.GetOptions{})
// ensure the finalizer(s) are removed
@@ -805,158 +804,6 @@ func NewCephInstaller(t func() *testing.T, clientset *kubernetes.Clientset, useH
return h
}
// Wipe cluster disks
func (h *CephInstaller) WipeDisks(namespace string, retries int) (err error) {
for i := 1; i <= retries; i++ {
if err = h.wipeClusterDisks(namespace); err == nil {
logger.Info("disks wiped successfully")
return nil
} else {
logger.Warningf("failed to wipe cluster disks. %+v. Attemp %d/%d ...", err, i, retries)
}
}
return fmt.Errorf("failed to wipe cluster disks. %+v", err)
}
// wipeClusterDisks runs a disk wipe job on all nodes in the k8s cluster.
func (h *CephInstaller) wipeClusterDisks(namespace string) error {
// Wipe clean disks on all nodes
nodes, err := h.GetNodeHostnames()
if err != nil {
return fmt.Errorf("failed to get node hostnames. %+v", err)
}
var jobNames []string
for _, node := range nodes {
// Start the new job
globalTestJobIndex++
jobName := fmt.Sprintf("rook-ceph-disk-wipe-%d", globalTestJobIndex)
jobNames = append(jobNames, jobName)
job := h.GetDiskWipeJob(node, jobName, namespace)
_, err := h.k8shelper.KubectlWithStdin(job, createFromStdinArgs...)
if err != nil {
return fmt.Errorf("failed to create disk wipe job for host %s. %+v", node, err)
}
}
allJobsAreComplete := func() (done bool, err error) {
for _, jobName := range jobNames {
j, err := h.k8shelper.Clientset.BatchV1().Jobs(namespace).Get(jobName, metav1.GetOptions{})
if err != nil {
return false, nil
}
if j.Status.Failed > 0 {
return false, fmt.Errorf("job %s failed", jobName)
}
if j.Status.Succeeded == 0 {
return false, nil
}
}
return true, nil
}
// return the error below after cleaning up the jobs
err = wait.Poll(5*time.Second, 90*time.Second, allJobsAreComplete)
if err != nil {
return fmt.Errorf("failed to wait for wipe jobs to complete. %+v", err)
}
return nil
}
// GetDiskWipeJob returns a YAML manifest string for a job which will wipe clean the extra
// (non-boot) disks on a node, allowing Ceph to use the disk(s) during its testing.
func (h *CephInstaller) GetDiskWipeJob(nodeName, jobName, namespace string) string {
// put the wipe job in the cluster namespace so that logs get picked up in failure conditions
return `apiVersion: batch/v1
kind: Job
metadata:
name: ` + jobName + `
namespace: ` + namespace + `
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: disk-wipe
image: ` + h.CephVersion.Image + `
securityContext:
privileged: true
volumeMounts:
- name: dev
mountPath: /dev
- name: run-udev
mountPath: /run/udev
command:
- "sh"
- "-c"
- |
set -xEeuo pipefail
# Wipe LVM data from disks
#
udevadm trigger || true
vgimport -a || true
# We should prepopulate the PVs corresponding to Ceph VGs here.
# It's because getting the relationship of the PVs and the VGs
# is impossible after removing the VGs.
TMP=$(pvs --noheadings --separator=',' -o pv_name,vg_name)
PVS=
for line in $TMP ; do
if [[ $line =~ ,ceph- ]]; then
PVS="$PVS ${line%,*}"
fi
done
# Wipe VGs
for vg in $(vgs --noheadings --readonly --separator=' ' -o vg_name); do
if [[ $vg =~ ^ceph- ]]; then
vgremove --yes --force "$vg"
fi
done
# Wipe PVs
for pv in $PVS; do
pvremove --yes --force "$pv"
wipefs --all "$pv"
dd if=/dev/zero of="$pv" bs=1M count=100 oflag=direct,dsync
# do not fail the timeout command
# the command seems to hang sometimes for no reason so let's retry
set +Ee
for i in $(seq 1 3); do
timeout --preserve-status 5s sgdisk --zap-all "$pv"
if [ "$?" -eq 0 ]; then
break
fi
sleep 5
done
set -Ee
done
# Wipe the specific disk in the CI that was running in raw mode
set +Ee
block=` + TestScratchDevice() + `
wipefs --all "$block"
dd if=/dev/zero of="$block" bs=1M count=100 oflag=direct,dsync
set -Ee
# Useful debug commands
lsblk
blkid
df /
#
# some devices might still be mapped that lock the disks
# this can fail with long-gone vestigial devices, so just assume this is successful
ls /dev/mapper/ceph-* | xargs -I% -- dmsetup remove --force % || true
rm -rf /dev/mapper/ceph-* # clutter
#
# ceph-volume setup also leaves ceph-UUID directories in /dev (just clutter)
rm -rf /dev/ceph-*
nodeSelector:
kubernetes.io/hostname: ` + nodeName + `
volumes:
- name: dev
hostPath:
path: /dev
- name: run-udev
hostPath:
path: /run/udev
`
}
// GetCleanupPod gets a cleanup Pod that cleans up the dataDirHostPath
func (h *CephInstaller) GetCleanupPod(node, removalDir string) string {
return `apiVersion: batch/v1
@@ -1049,10 +896,18 @@ func (h *CephInstaller) waitForCleanupJobs(namespace string) error {
return false, nil
}
for _, job := range cleanupJobs.Items {
logger.Debugf("job %q status: %+v", job.Name, job.Status)
logger.Infof("job %q status: %+v", job.Name, job.Status)
if job.Status.Failed > 0 {
return false, fmt.Errorf("job %s failed", job.Name)
}
if job.Status.Succeeded == 1 {
l, err := h.k8shelper.Kubectl("-n", namespace, "logs", fmt.Sprintf("job.batch/%s", job.Name))
if err != nil {
logger.Errorf("cannot get logs for pod %s. %v", job.Name, err)
}
rawData := []byte(l)
logger.Infof("cleanup job %s done. logs: %s", job.Name, string(rawData))
}
if job.Status.Succeeded == 0 {
return false, nil
}
@@ -254,6 +254,17 @@ spec:
confirmation:
type: string
pattern: ^$|^yes-really-destroy-data$
sanitizeDisks:
properties:
method:
type: string
pattern: ^(complete|quick)$
dataSource:
type: string
pattern: ^(zero|random)$
iteration:
type: integer
format: int32
placement: {}
resources: {}
additionalPrinterColumns:
+1 -1
View File
@@ -92,7 +92,7 @@ func (s *CephFlexDriverSuite) SetupSuite() {
usePVC: false,
mons: 1,
rbdMirrorWorkers: 1,
rookCephCleanup: false,
rookCephCleanup: true,
skipOSDCreation: false,
minimalMatrixK8sVersion: flexDriverMinimalTestVersion,
rookVersion: installer.VersionMaster,