In order to help users check that they have implemented the newly-added Multus host configuration prerequisites, add a check to the validation tool to verify connectivity. Because users who are already running clusters with Multus enabled, add a flag that allows users to only check for host configuration prerequisites. This mode will not start the large number of clients that would normally be started because those clients could disrupt a running Rook cluster negatively. Host checking pods require host network access. Many Kubernetes distributions have pod security features enabled. In order to allow non-Vanilla distros to run this tool, allow specifying a service account that pods will run as, which can be configured by the admin to allow test pods. Signed-off-by: Blaine Gardner <blaine.gardner@ibm.com>
291 lines
8.1 KiB
Go
291 lines
8.1 KiB
Go
/*
|
|
Copyright 2023 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 multus
|
|
|
|
import (
|
|
"bytes"
|
|
_ "embed"
|
|
"fmt"
|
|
"strings"
|
|
"text/template"
|
|
|
|
apps "k8s.io/api/apps/v1"
|
|
core "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/util/yaml"
|
|
)
|
|
|
|
var (
|
|
//go:embed nginx-pod.yaml
|
|
nginxPodTemplate string
|
|
|
|
//go:embed nginx-config.yaml
|
|
nginxConfigTemplate string
|
|
|
|
//go:embed image-pull-daemonset.yaml
|
|
imagePullDaemonSet string
|
|
|
|
//go:embed host-daemonset.yaml
|
|
hostCheckerDaemonSet string
|
|
|
|
//go:embed client-daemonset.yaml
|
|
clientDaemonSet string
|
|
)
|
|
|
|
type webServerTemplateConfig struct {
|
|
NetworksAnnotationValue string
|
|
NginxImage string
|
|
Placement PlacementConfig
|
|
}
|
|
|
|
type imagePullTemplateConfig struct {
|
|
NodeType string
|
|
NginxImage string
|
|
Placement PlacementConfig
|
|
}
|
|
|
|
type hostCheckerTemplateConfig struct {
|
|
NodeType string
|
|
NginxImage string
|
|
PublicNetworkAddress string
|
|
Placement PlacementConfig
|
|
}
|
|
|
|
type clientTemplateConfig struct {
|
|
NodeType string
|
|
ClientType string
|
|
ClientID int
|
|
NetworksAnnotationValue string
|
|
NetworkNamesAndAddresses map[string]string
|
|
NginxImage string
|
|
Placement PlacementConfig
|
|
}
|
|
|
|
func webServerPodName() string {
|
|
return "multus-validation-test-web-server"
|
|
}
|
|
|
|
func imagePullAppLabel() string {
|
|
return "app=multus-validation-test-image-pull"
|
|
}
|
|
|
|
func hostCheckerAppLabel() string {
|
|
return "app=multus-validation-test-host-checker"
|
|
}
|
|
|
|
func getNodeType(m *metav1.ObjectMeta) string {
|
|
return m.GetLabels()["nodeType"]
|
|
}
|
|
|
|
func clientAppLabel() string {
|
|
return "app=multus-validation-test-client"
|
|
}
|
|
|
|
const (
|
|
ClientTypeOSD = "osd"
|
|
ClientTypeNonOSD = "other"
|
|
)
|
|
|
|
type daemonsetAppType string
|
|
|
|
const imagePullDaemonSetAppType = "image pull"
|
|
const hostCheckerDaemonsetAppType = "host checker"
|
|
const clientDaemonSetAppType = "client"
|
|
|
|
func (vt *ValidationTest) generateWebServerTemplateConfig(placement PlacementConfig) webServerTemplateConfig {
|
|
return webServerTemplateConfig{
|
|
NetworksAnnotationValue: vt.generateNetworksAnnotationValue(true, true), // always on both nets
|
|
NginxImage: vt.NginxImage,
|
|
Placement: placement,
|
|
}
|
|
}
|
|
|
|
func (vt *ValidationTest) generateHostCheckerTemplateConfig(
|
|
serverPublicAddr string,
|
|
nodeType string,
|
|
placement PlacementConfig,
|
|
) hostCheckerTemplateConfig {
|
|
return hostCheckerTemplateConfig{
|
|
NodeType: nodeType,
|
|
PublicNetworkAddress: serverPublicAddr,
|
|
NginxImage: vt.NginxImage,
|
|
Placement: placement,
|
|
}
|
|
}
|
|
|
|
func (vt *ValidationTest) generateClientTemplateConfig(
|
|
attachPublic, attachCluster bool,
|
|
serverPublicAddr, serverClusterAddr string,
|
|
nodeType, clientType string,
|
|
clientID int,
|
|
placement PlacementConfig,
|
|
) clientTemplateConfig {
|
|
netNamesAndAddresses := map[string]string{}
|
|
if attachPublic && serverPublicAddr != "" {
|
|
netNamesAndAddresses["public"] = serverPublicAddr
|
|
}
|
|
if attachCluster && serverClusterAddr != "" {
|
|
netNamesAndAddresses["cluster"] = serverClusterAddr
|
|
}
|
|
for name, addr := range netNamesAndAddresses {
|
|
if strings.Contains(addr, ":") {
|
|
// it's an IPv6 address and needs square brackets around it to support :<port> addition
|
|
netNamesAndAddresses[name] = "[" + addr + "]"
|
|
}
|
|
}
|
|
return clientTemplateConfig{
|
|
NodeType: nodeType,
|
|
ClientType: clientType,
|
|
ClientID: clientID,
|
|
NetworksAnnotationValue: vt.generateNetworksAnnotationValue(attachPublic, attachCluster),
|
|
NetworkNamesAndAddresses: netNamesAndAddresses,
|
|
NginxImage: vt.NginxImage,
|
|
Placement: placement,
|
|
}
|
|
}
|
|
|
|
func (vt *ValidationTest) generateImagePullTemplateConfig(nodeType string, placement PlacementConfig) imagePullTemplateConfig {
|
|
return imagePullTemplateConfig{
|
|
NodeType: nodeType,
|
|
NginxImage: vt.NginxImage,
|
|
Placement: placement,
|
|
}
|
|
}
|
|
|
|
func (vt *ValidationTest) generateWebServerPod(placement PlacementConfig) (*core.Pod, error) {
|
|
t, err := loadTemplate("webServerPod", nginxPodTemplate, vt.generateWebServerTemplateConfig(placement))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load web server pod template: %w", err)
|
|
}
|
|
|
|
var p core.Pod
|
|
err = yaml.Unmarshal(t, &p)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal web server pod template: %w", err)
|
|
}
|
|
|
|
vt.applyServiceAccountToPodSpec(&p.Spec)
|
|
|
|
return &p, nil
|
|
}
|
|
|
|
func (vt *ValidationTest) generateWebServerConfigMap() (*core.ConfigMap, error) {
|
|
t, err := loadTemplate("webServerConfigMap", nginxConfigTemplate, vt.generateWebServerTemplateConfig(
|
|
PlacementConfig{}, // not used for configmap
|
|
))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load web server configmap template: %w", err)
|
|
}
|
|
|
|
var cm core.ConfigMap
|
|
err = yaml.Unmarshal(t, &cm)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal web server configmap template: %w", err)
|
|
}
|
|
|
|
return &cm, nil
|
|
}
|
|
|
|
func (vt *ValidationTest) generateImagePullDaemonSet(nodeType string, placement PlacementConfig) (*apps.DaemonSet, error) {
|
|
t, err := loadTemplate("imagePullDaemonSet", imagePullDaemonSet, vt.generateImagePullTemplateConfig(nodeType, placement))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load image pull daemonset template: %w", err)
|
|
}
|
|
|
|
var d apps.DaemonSet
|
|
err = yaml.Unmarshal(t, &d)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal image pull daemonset template: %w", err)
|
|
}
|
|
|
|
vt.applyServiceAccountToPodSpec(&d.Spec.Template.Spec)
|
|
|
|
return &d, nil
|
|
}
|
|
|
|
func (vt *ValidationTest) generateHostCheckerDaemonSet(
|
|
serverPublicAddr string,
|
|
nodeType string,
|
|
placement PlacementConfig,
|
|
) (*apps.DaemonSet, error) {
|
|
t, err := loadTemplate("hostCheckerDaemonSet", hostCheckerDaemonSet, vt.generateHostCheckerTemplateConfig(serverPublicAddr, nodeType, placement))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load host checker daemonset template: %w", err)
|
|
}
|
|
|
|
var d apps.DaemonSet
|
|
err = yaml.Unmarshal(t, &d)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal host checker daemonset template: %w", err)
|
|
}
|
|
|
|
vt.applyServiceAccountToPodSpec(&d.Spec.Template.Spec)
|
|
|
|
return &d, nil
|
|
}
|
|
|
|
func (vt *ValidationTest) generateClientDaemonSet(
|
|
attachPublic, attachCluster bool,
|
|
serverPublicAddr, serverClusterAddr string,
|
|
nodeType, clientType string,
|
|
clientID int,
|
|
placement PlacementConfig,
|
|
) (*apps.DaemonSet, error) {
|
|
t, err := loadTemplate("clientDaemonSet", clientDaemonSet, vt.generateClientTemplateConfig(attachPublic, attachCluster, serverPublicAddr, serverClusterAddr, nodeType, clientType, clientID, placement))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load client daemonset template: %w", err)
|
|
}
|
|
|
|
var d apps.DaemonSet
|
|
err = yaml.Unmarshal(t, &d)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal client daemonset template: %w", err)
|
|
}
|
|
|
|
vt.applyServiceAccountToPodSpec(&d.Spec.Template.Spec)
|
|
|
|
return &d, nil
|
|
}
|
|
|
|
func (vt *ValidationTest) generateNetworksAnnotationValue(public, cluster bool) string {
|
|
nets := []string{}
|
|
if public && vt.PublicNetwork != "" {
|
|
nets = append(nets, vt.PublicNetwork)
|
|
}
|
|
if cluster && vt.ClusterNetwork != "" {
|
|
nets = append(nets, vt.ClusterNetwork)
|
|
}
|
|
return strings.Join(nets, ",")
|
|
}
|
|
|
|
func (vt *ValidationTest) applyServiceAccountToPodSpec(ps *core.PodSpec) {
|
|
if vt.ServiceAccountName != "" {
|
|
ps.ServiceAccountName = vt.ServiceAccountName
|
|
}
|
|
}
|
|
|
|
func loadTemplate(name, templateFileText string, config interface{}) ([]byte, error) {
|
|
var writer bytes.Buffer
|
|
t := template.New(name)
|
|
t, err := t.Parse(templateFileText)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse template %q: %w", name, err)
|
|
}
|
|
err = t.Execute(&writer, config)
|
|
return writer.Bytes(), err
|
|
}
|