forked from rook/rook
Following the Ceph OSD rework, my test environment regularly failed to start OSDs, as they would start before the postStart lifecyle hook which was chown'ing the log and data directories could finish. Pods failed to start for an arbitrarily long time before starting successfully. This was a manifestation of a race condition identified in Ceph monitors in https://github.com/rook/rook/pull/3594#discussion_r312279176 Create a Ceph operator-level method to create an init container that will chown the necessary directories for all Ceph daemons (any daemon which runs as the ceph:ceph user-and-group). Also add this container to expectations for Ceph pod templates in unit tests. Signed-off-by: Blaine Gardner <blaine.gardner@suse.com>
157 lines
5.9 KiB
Go
157 lines
5.9 KiB
Go
/*
|
|
Copyright 2019 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 test
|
|
|
|
/*
|
|
The goal here is not to test every individual specification of the pod/container. Testing that the
|
|
generated pod spec has each piece set in the Rook code isn't a particularly effective use of unit
|
|
tests. Any time the Rook code changes the pod spec intentionally, the unit test changes in the
|
|
exact same way, which doesn't really help prevent against errors where devs are changing the wrong
|
|
spec values.
|
|
|
|
Instead, the unit tests should focus on testing things that are universal truths about
|
|
Ceph pod specifications that can help catch when pods ...
|
|
- do not have the minimum requirements for running Ceph tools/daemons
|
|
- have vestigial values set that are no longer needed
|
|
- have references to absent resources (e.g., a volume mount without a volume source)
|
|
|
|
In this way, unit tests for pod specifications can be consistent and shared between all Ceph pods
|
|
created by the Rook operator. With this consistency between unit tests, there should be increased
|
|
consistency between the Ceph pods that Rook creates, ensuring a consistent user experience
|
|
interacting with pods.
|
|
*/
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/rook/rook/pkg/operator/ceph/config"
|
|
optest "github.com/rook/rook/pkg/operator/test"
|
|
"github.com/stretchr/testify/assert"
|
|
v1 "k8s.io/api/core/v1"
|
|
)
|
|
|
|
// A PodSpecTester is a helper exposing methods for testing required Ceph specifications common for
|
|
// all Ceph PodSpecs.
|
|
type PodSpecTester struct {
|
|
t *testing.T
|
|
spec *v1.PodSpec
|
|
}
|
|
|
|
// Spec creates a PodSpecTester from a parent PodTemplateSpecTester.
|
|
func (pt *PodTemplateSpecTester) Spec() *PodSpecTester {
|
|
return NewPodSpecTester(pt.t, &pt.template.Spec)
|
|
}
|
|
|
|
// NewPodSpecTester creates a new tester to test the given PodSpec.
|
|
func NewPodSpecTester(t *testing.T, spec *v1.PodSpec) *PodSpecTester {
|
|
return &PodSpecTester{t: t, spec: spec}
|
|
}
|
|
|
|
// AssertVolumesMeetCephRequirements asserts that all the required Ceph volumes exist in the pod
|
|
// spec under test, Volumes list.
|
|
func (ps *PodSpecTester) AssertVolumesMeetCephRequirements(
|
|
daemonType config.DaemonType, daemonID string,
|
|
) {
|
|
keyringSecretName := fmt.Sprintf("rook-ceph-%s-%s-keyring", daemonType, daemonID)
|
|
if daemonType == config.MonType {
|
|
keyringSecretName = "rook-ceph-mons-keyring" // mons share a keyring
|
|
}
|
|
requiredVols := []string{"rook-config-override", keyringSecretName}
|
|
if daemonType != config.RbdMirrorType {
|
|
requiredVols = append(requiredVols, "ceph-daemon-data")
|
|
}
|
|
vols := []string{}
|
|
|
|
for _, v := range ps.spec.Volumes {
|
|
vols = append(vols, v.Name)
|
|
switch v.Name {
|
|
case "ceph-daemon-data":
|
|
switch daemonType {
|
|
case config.MonType:
|
|
// mons may be host path or pvc
|
|
assert.True(ps.t,
|
|
v.VolumeSource.HostPath != nil || v.VolumeSource.PersistentVolumeClaim != nil,
|
|
string(daemonType)+" daemon should be host path or pvc:", v)
|
|
case config.OsdType:
|
|
// osds MUST be host path
|
|
assert.NotNil(ps.t, v.VolumeSource.HostPath,
|
|
string(daemonType)+" daemon should be host path:", v)
|
|
case config.MgrType, config.MdsType, config.RgwType:
|
|
// mgrs, mdses, and rgws MUST be host path
|
|
assert.NotNil(ps.t, v.VolumeSource.EmptyDir,
|
|
string(daemonType)+" daemon should be empty dir:", v)
|
|
}
|
|
case "rook-ceph-config":
|
|
assert.Equal(ps.t, "rook-ceph-config", v.VolumeSource.ConfigMap.LocalObjectReference.Name,
|
|
"Ceph config volume source is wrong path:", v)
|
|
case keyringSecretName:
|
|
assert.Equal(ps.t, keyringSecretName, v.VolumeSource.Secret.SecretName,
|
|
"daemon keyring volume source is wrong path:", v)
|
|
}
|
|
}
|
|
assert.Subset(ps.t, vols, requiredVols,
|
|
"required volumes don't exist in pod spec's volume list:", ps.spec.Volumes)
|
|
}
|
|
|
|
// AssertRestartPolicyAlways asserts that the pod spec is set to always restart on failure.
|
|
func (ps *PodSpecTester) AssertRestartPolicyAlways() {
|
|
assert.Equal(ps.t, v1.RestartPolicyAlways, ps.spec.RestartPolicy)
|
|
}
|
|
|
|
// AssertChownContainer ensures that the init container to chown the Ceph data dir is present for
|
|
// Ceph daemons.
|
|
func (ps *PodSpecTester) AssertChownContainer(daemonType config.DaemonType) {
|
|
switch daemonType {
|
|
case config.MonType, config.MgrType, config.OsdType, config.MdsType, config.RgwType, config.RbdMirrorType:
|
|
assert.True(ps.t, containerExists("chown-container-data-dir", ps.spec))
|
|
}
|
|
}
|
|
|
|
// RunFullSuite runs all assertion tests for the PodSpec under test and its sub-resources.
|
|
func (ps *PodSpecTester) RunFullSuite(
|
|
daemonType config.DaemonType,
|
|
resourceName, cephImage, cpuResourceLimit, cpuResourceRequest, memoryResourceLimit, memoryResourceRequest string,
|
|
) {
|
|
resourceExpectations := optest.ResourceLimitExpectations{
|
|
CPUResourceLimit: cpuResourceLimit,
|
|
MemoryResourceLimit: memoryResourceLimit,
|
|
CPUResourceRequest: cpuResourceRequest,
|
|
MemoryResourceRequest: memoryResourceRequest,
|
|
}
|
|
ops := optest.NewPodSpecTester(ps.t, ps.spec)
|
|
ops.RunFullSuite(resourceExpectations)
|
|
|
|
ps.AssertVolumesMeetCephRequirements(daemonType, resourceName)
|
|
ps.AssertRestartPolicyAlways()
|
|
ps.AssertChownContainer(daemonType)
|
|
ps.Containers().RunFullSuite(cephImage, cpuResourceLimit, cpuResourceRequest, memoryResourceLimit, memoryResourceRequest)
|
|
}
|
|
|
|
func allContainers(p *v1.PodSpec) []v1.Container {
|
|
return append(p.InitContainers, p.Containers...)
|
|
}
|
|
|
|
func containerExists(containerName string, p *v1.PodSpec) bool {
|
|
for _, c := range p.InitContainers {
|
|
if c.Name == containerName {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|