Skip to content
This repository has been archived by the owner on Nov 9, 2022. It is now read-only.
This repository is currently being migrated. It's locked while the migration is in progress.

[WIP] storageos: Add compute-only deployment support #95

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pkg/apis/storageos/v1/storageoscluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ type StorageOSClusterSpec struct {
// node affinity requiredDuringSchedulingIgnoredDuringExecution.
NodeSelectorTerms []corev1.NodeSelectorTerm `json:"nodeSelectorTerms"`

// ComputeOnlyNodeSelectorTerms is to set the placement of storageos compute
// only pods using node affinity
// requiredDuringSchedulingIgnoredDuringExecution.
ComputeOnlyNodeSelectorTerms []corev1.NodeSelectorTerm `json:"computeOnlyNodeSelectorTerms"`

// Tolerations is to set the placement of storageos pods using
// pod toleration.
Tolerations []corev1.Toleration `json:"tolerations"`
Expand Down
17 changes: 12 additions & 5 deletions pkg/apis/storageos/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 45 additions & 5 deletions pkg/storageos/daemonset.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,49 @@ import (
"k8s.io/apimachinery/pkg/util/intstr"
)

const (
// Names of the storageos daemonset resources.
daemonsetName = "storageos-daemonset"
computeOnlyDaemonsetName = "storageos-compute-only"
)

// createDaemonSet creates storageos storage daemonset.
func (s *Deployment) createDaemonSet() error {
dset, err := s.getBasicDaemonSetConfiguration(daemonsetName)
if err != nil {
return err
}

s.addNodeAffinity(&dset.Spec.Template.Spec, s.stos.Spec.NodeSelectorTerms)

return s.createOrUpdateObject(dset)
}

// createComputeOnlyDaemonSet creates storageos compute only daemonset.
func (s *Deployment) createComputeOnlyDaemonSet() error {
// Check if node selector terms for compute only is specified.
if len(s.stos.Spec.ComputeOnlyNodeSelectorTerms) < 1 {
return nil
}

dset, err := s.getBasicDaemonSetConfiguration(computeOnlyDaemonsetName)
if err != nil {
return err
}

podSpec := &dset.Spec.Template.Spec
nodeContainer := &podSpec.Containers[0]

// Pass compute-only label.
nodeContainer.Env = s.addStorageOSLabelsEnvVars(nodeContainer.Env, computeOnlyLabelVal)

s.addNodeAffinity(podSpec, s.stos.Spec.ComputeOnlyNodeSelectorTerms)

return s.createOrUpdateObject(dset)
}

// getBasicDaemonSet creates a basic daemonset configuration for storageos.
func (s *Deployment) getBasicDaemonSetConfiguration(name string) (*appsv1.DaemonSet, error) {
ls := labelsForDaemonSet(s.stos.Name)
privileged := true
mountPropagationBidirectional := corev1.MountPropagationBidirectional
Expand All @@ -21,7 +63,7 @@ func (s *Deployment) createDaemonSet() error {
Kind: "DaemonSet",
},
ObjectMeta: metav1.ObjectMeta{
Name: daemonsetName,
Name: name,
Namespace: s.stos.Spec.GetResourceNS(),
Labels: map[string]string{
"app": "storageos",
Expand Down Expand Up @@ -217,10 +259,8 @@ func (s *Deployment) createDaemonSet() error {
podSpec := &dset.Spec.Template.Spec
nodeContainer := &podSpec.Containers[0]

s.addNodeAffinity(podSpec)

if err := s.addTolerations(podSpec); err != nil {
return err
return nil, err
}

nodeContainer.Env = s.addKVBackendEnvVars(nodeContainer.Env)
Expand All @@ -233,7 +273,7 @@ func (s *Deployment) createDaemonSet() error {

s.addCSI(podSpec)

return s.createOrUpdateObject(dset)
return dset, nil
}

func (s *Deployment) deleteDaemonSet(name string) error {
Expand Down
45 changes: 42 additions & 3 deletions pkg/storageos/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"log"
"strings"

"github.com/blang/semver"
storageosv1 "github.com/storageos/cluster-operator/pkg/apis/storageos/v1"
Expand All @@ -24,7 +25,6 @@ const (
daemonsetKind = "daemonset"
statefulsetKind = "statefulset"

daemonsetName = "storageos-daemonset"
statefulsetName = "storageos-statefulset"

tlsSecretType = "kubernetes.io/tls"
Expand All @@ -36,6 +36,7 @@ const (
hostnameEnvVar = "HOSTNAME"
adminUsernameEnvVar = "ADMIN_USERNAME"
adminPasswordEnvVar = "ADMIN_PASSWORD"
labelsEnvVar = "LABELS"
joinEnvVar = "JOIN"
advertiseIPEnvVar = "ADVERTISE_IP"
namespaceEnvVar = "NAMESPACE"
Expand All @@ -61,8 +62,9 @@ const (
kvBackendEnvVar = "KV_BACKEND"
debugEnvVar = "LOG_LEVEL"

sysAdminCap = "SYS_ADMIN"
debugVal = "xdebug"
sysAdminCap = "SYS_ADMIN"
debugVal = "xdebug"
computeOnlyLabelVal = "storageos.com/deployment=computeonly"

defaultFSType = "ext4"
secretNamespaceKey = "adminSecretNamespace"
Expand Down Expand Up @@ -120,6 +122,12 @@ func (s *Deployment) Deploy() error {
return err
}

// Compute-only daemonset.
if err := s.createComputeOnlyDaemonSet(); err != nil {
return err
}

// Storage daemonset.
if err := s.createDaemonSet(); err != nil {
return err
}
Expand Down Expand Up @@ -318,6 +326,37 @@ func getCSICredsEnvVar(envVarName, secretName, key string) corev1.EnvVar {
}
}

// addStorageOSLabelsEnvVars checks if the debug mode is set and set the appropriate env var.
func (s Deployment) addStorageOSLabelsEnvVars(env []corev1.EnvVar, labels string) []corev1.EnvVar {
// Return the argument env var if no labels are specified.
if len(labels) == 0 {
return env
}

// Check if labels env var already exists.
labelsExists := false
for _, envVar := range env {
if envVar.Name == labelsEnvVar {
// Append the label with new label entries.
// The labels are separated by ",".
// e.g.: "storageos.com/deployment=computeonly,country=us,env=prod"
envVar.Value = strings.Join([]string{envVar.Value, labels}, ",")
labelsExists = true
break
}
}

// Add new labels env var if it doesn't exists.
if !labelsExists {
labelsEnvVar := corev1.EnvVar{
Name: labelsEnvVar,
Value: labels,
}
return append(env, labelsEnvVar)
}
return env
}

// createOrUpdateObject attempts to create a given object. If the object already
// exists and `Deployment.update` is false, no change is made. If update is true,
// the existing object is updated.
Expand Down
121 changes: 121 additions & 0 deletions pkg/storageos/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"reflect"
"strconv"
"strings"
"testing"

appsv1 "k8s.io/api/apps/v1"
Expand Down Expand Up @@ -1137,3 +1138,123 @@ func TestDelete(t *testing.T) {
t.Fatal("failed to get the created namespace", err)
}
}

func TestDeployComputeOnlyNodes(t *testing.T) {
nodeSelectorTerms := []corev1.NodeSelectorTerm{
{
MatchExpressions: []corev1.NodeSelectorRequirement{
{
Key: "foo",
Operator: corev1.NodeSelectorOpIn,
Values: []string{"baz"},
},
},
},
}

testcases := []struct {
name string
nodeSelectorTerms []corev1.NodeSelectorTerm
computeOnlyNodeSelectorTerms []corev1.NodeSelectorTerm
wantComputeOnly bool
}{
{
name: "2 daemonsets",
nodeSelectorTerms: nodeSelectorTerms,
computeOnlyNodeSelectorTerms: []corev1.NodeSelectorTerm{
{
MatchExpressions: []corev1.NodeSelectorRequirement{
{
Key: "storageos.com/deployment",
Operator: corev1.NodeSelectorOpIn,
Values: []string{"computeonly"},
},
},
},
},
wantComputeOnly: true,
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
stosCluster := &api.StorageOSCluster{
TypeMeta: metav1.TypeMeta{
APIVersion: gvk.GroupVersion().String(),
Kind: gvk.Kind,
},
ObjectMeta: metav1.ObjectMeta{
Name: "teststos",
Namespace: "default",
},
Spec: api.StorageOSClusterSpec{
CSI: api.StorageOSClusterCSI{
Enable: true,
},
NodeSelectorTerms: tc.nodeSelectorTerms,
ComputeOnlyNodeSelectorTerms: tc.computeOnlyNodeSelectorTerms,
},
}

c := fake.NewFakeClientWithScheme(testScheme)
if err := c.Create(context.Background(), stosCluster); err != nil {
t.Fatalf("failed to create storageoscluster object: %v", err)
}

deploy := NewDeployment(c, stosCluster, nil, testScheme, "", false)
err := deploy.Deploy()
if err != nil {
t.Error("deployment failed:", err)
}

daemonset1 := &appsv1.DaemonSet{}
nsName1 := types.NamespacedName{
Name: daemonsetName,
Namespace: defaultNS,
}

if err := c.Get(context.Background(), nsName1, daemonset1); err != nil {
t.Fatal("failed to get the created daemonset", err)
}

// Check if storage daemonset contains compute-only label.
for _, envvar := range daemonset1.Spec.Template.Spec.Containers[0].Env {
if envvar.Name != labelsEnvVar {
continue
}
if strings.Contains(envvar.Value, computeOnlyLabelVal) {
t.Errorf("expected %q to not be in node labels", computeOnlyLabelVal)
}
}

daemonset2 := &appsv1.DaemonSet{}
nsName2 := types.NamespacedName{
Name: computeOnlyDaemonsetName,
Namespace: defaultNS,
}

if err := c.Get(context.Background(), nsName2, daemonset2); err != nil {
t.Fatal("failed to get the created daemonset", err)
}

// Check if storage daemonset contains compute-only label.
foundComputeOnly := false
for _, envvar := range daemonset2.Spec.Template.Spec.Containers[0].Env {
if envvar.Name != labelsEnvVar {
continue
}
if !strings.Contains(envvar.Value, computeOnlyLabelVal) {
t.Errorf("expected %q to be in node labels", computeOnlyLabelVal)
} else {
foundComputeOnly = true
}
}

// Extra check to ensure compute compute only was found. The above
// check would be skipped if there's no LABELS env var.
if !foundComputeOnly {
t.Error("expected to find computeonly label")
}
})
}
}
6 changes: 3 additions & 3 deletions pkg/storageos/podspec.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,11 +252,11 @@ func (s *Deployment) addCSI(podSpec *corev1.PodSpec) {

// addNodeAffinity adds node affinity to the given pod spec from the cluster
// spec NodeSelectorLabel.
func (s *Deployment) addNodeAffinity(podSpec *corev1.PodSpec) {
if len(s.stos.Spec.NodeSelectorTerms) > 0 {
func (s Deployment) addNodeAffinity(podSpec *corev1.PodSpec, nsTerms []corev1.NodeSelectorTerm) {
if len(nsTerms) > 0 {
podSpec.Affinity = &corev1.Affinity{NodeAffinity: &corev1.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
NodeSelectorTerms: s.stos.Spec.NodeSelectorTerms,
NodeSelectorTerms: nsTerms,
},
}}
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/storageos/statefulset.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func (s *Deployment) createStatefulSet() error {

podSpec := &sset.Spec.Template.Spec

s.addNodeAffinity(podSpec)
s.addNodeAffinity(podSpec, s.stos.Spec.NodeSelectorTerms)

if err := s.addTolerations(podSpec); err != nil {
return err
Expand Down