Skip to content

Commit 264b9a2

Browse files
committed
Add Longhorn installation tests
Signed-off-by: hamistao <[email protected]>
1 parent 8953881 commit 264b9a2

File tree

1 file changed

+264
-0
lines changed

1 file changed

+264
-0
lines changed
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
//go:build validation
2+
3+
package longhorn
4+
5+
import (
6+
"fmt"
7+
"strings"
8+
"testing"
9+
10+
"github.com/rancher/norman/types"
11+
"github.com/rancher/shepherd/clients/rancher"
12+
"github.com/rancher/shepherd/clients/rancher/catalog"
13+
management "github.com/rancher/shepherd/clients/rancher/generated/management/v3"
14+
steveV1 "github.com/rancher/shepherd/clients/rancher/v1"
15+
shepherd_charts "github.com/rancher/shepherd/extensions/charts"
16+
"github.com/rancher/shepherd/extensions/clusters"
17+
"github.com/rancher/shepherd/extensions/defaults/namespaces"
18+
"github.com/rancher/shepherd/extensions/kubeconfig"
19+
"github.com/rancher/shepherd/extensions/kubectl"
20+
"github.com/rancher/shepherd/extensions/workloads/pods"
21+
"github.com/rancher/shepherd/pkg/namegenerator"
22+
"github.com/rancher/shepherd/pkg/session"
23+
"github.com/rancher/tests/actions/charts"
24+
"github.com/rancher/tests/actions/cloudprovider"
25+
namespace_actions "github.com/rancher/tests/actions/namespaces"
26+
"github.com/stretchr/testify/require"
27+
"github.com/stretchr/testify/suite"
28+
appv1 "k8s.io/api/apps/v1"
29+
corev1 "k8s.io/api/core/v1"
30+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31+
"k8s.io/client-go/rest"
32+
)
33+
34+
var (
35+
longhornNamespace = "longhorn-system"
36+
longhornChartName = "longhorn"
37+
longhornProjectName = "longhorn-test"
38+
longhornDefaultStorageClassName = "longhorn"
39+
debugNamespace = namegenerator.AppendRandomString("debug-")
40+
longhornCustomSetting = map[string]any{
41+
"defaultSettings": map[string]any{
42+
"createDefaultDiskLabeledNodes": true,
43+
"defaultDataPath": "/var/lib/longhorn-custom",
44+
"defaultReplicaCount": 2,
45+
"storageOverProvisioningPercentage": 150,
46+
},
47+
}
48+
)
49+
50+
type LonghornTestSuite struct {
51+
suite.Suite
52+
client *rancher.Client
53+
session *session.Session
54+
cluster *clusters.ClusterMeta
55+
project *management.Project
56+
payloadOpts charts.PayloadOpts
57+
installedLonghorn bool
58+
}
59+
60+
func (l *LonghornTestSuite) TearDownSuite() {
61+
l.session.Cleanup()
62+
}
63+
64+
func (l *LonghornTestSuite) SetupSuite() {
65+
l.session = session.NewSession()
66+
67+
client, err := rancher.NewClient("", l.session)
68+
require.NoError(l.T(), err)
69+
70+
l.client = client
71+
72+
l.cluster, err = clusters.NewClusterMeta(client, client.RancherConfig.ClusterName)
73+
require.NoError(l.T(), err)
74+
75+
projectConfig := &management.Project{
76+
ClusterID: l.cluster.ID,
77+
Name: longhornProjectName,
78+
}
79+
80+
l.project, err = client.Management.Project.Create(projectConfig)
81+
require.NoError(l.T(), err)
82+
83+
// Get latest versions of longhorn
84+
latestLonghornVersion, err := l.client.Catalog.GetLatestChartVersion(longhornChartName, catalog.RancherChartRepo)
85+
require.NoError(l.T(), err)
86+
87+
l.payloadOpts = charts.PayloadOpts{
88+
Namespace: longhornNamespace,
89+
Host: l.client.RancherConfig.Host,
90+
InstallOptions: charts.InstallOptions{
91+
Cluster: l.cluster,
92+
Version: latestLonghornVersion,
93+
ProjectID: l.project.ID,
94+
},
95+
}
96+
}
97+
98+
func (l *LonghornTestSuite) TestChartInstall() {
99+
chart, err := shepherd_charts.GetChartStatus(l.client, l.cluster.ID, longhornNamespace, longhornChartName)
100+
require.NoError(l.T(), err)
101+
102+
if chart.IsAlreadyInstalled {
103+
l.T().Skip("Skipping installation test because Longhorn is already installed")
104+
}
105+
106+
l.T().Logf("Installing Lonhgorn chart in cluster [%v] with latest version [%v] in project [%v] and namespace [%v]", l.cluster.Name, l.payloadOpts.Version, l.project.Name, l.payloadOpts.Namespace)
107+
err = charts.InstallLonghornChart(l.client, l.payloadOpts, nil)
108+
require.NoError(l.T(), err)
109+
l.installedLonghorn = true
110+
}
111+
112+
func (l *LonghornTestSuite) TestChartInstallCustomConfig() {
113+
chart, err := shepherd_charts.GetChartStatus(l.client, l.cluster.ID, longhornNamespace, longhornChartName)
114+
require.NoError(l.T(), err)
115+
116+
// If Longhorn was installed by a previous test on this same session, uninstall it to install it again with custom configuration.
117+
// If Longhorn was installed previously to this test run, leave it be and skip this test. This way we allow for running the
118+
// next tests on top of a manually installed Longhorn and avoid accidentally uninstalling something important.
119+
if chart.IsAlreadyInstalled {
120+
if l.installedLonghorn {
121+
l.T().Log("Uninstalling Longhorn that was intalled on the previous test.")
122+
err = charts.UninstallLonghornChart(l.client, longhornNamespace, l.cluster.ID, l.payloadOpts.Host)
123+
require.NoError(l.T(), err)
124+
} else {
125+
l.T().Skip("Skipping installation test because Longhorn is already installed")
126+
}
127+
}
128+
129+
nodeCollection, err := l.client.Management.Node.List(&types.ListOpts{Filters: map[string]interface{}{
130+
"clusterId": l.cluster.ID,
131+
}})
132+
require.NoError(l.T(), err)
133+
134+
// Label worker nodes to check effectiveness of createDefaultDiskLabeledNodes setting.
135+
// Also save the name of one worker node for future use.
136+
l.T().Log("Label worker nodes with Longhorn's create-default-disk=true")
137+
var workerName string
138+
for _, node := range nodeCollection.Data {
139+
if node.Worker {
140+
labelNodeCommand := []string{"kubectl", "label", "node", node.Hostname, "node.longhorn.io/create-default-disk=true"}
141+
_, err = kubectl.Command(l.client, nil, l.cluster.ID, labelNodeCommand, "")
142+
require.NoError(l.T(), err)
143+
if workerName == "" {
144+
workerName = node.Hostname
145+
}
146+
}
147+
}
148+
149+
l.T().Logf("Installing Lonhgorn chart in cluster [%v] with latest version [%v] in project [%v] and namespace [%v]", l.cluster.Name, l.payloadOpts.Version, l.project.Name, l.payloadOpts.Namespace)
150+
err = charts.InstallLonghornChart(l.client, l.payloadOpts, longhornCustomSetting)
151+
require.NoError(l.T(), err)
152+
153+
settings := map[string]string{
154+
"default-data-path": "/var/lib/longhorn-custom",
155+
"default-replica-count": "2",
156+
"storage-over-provisioning-percentage": "150",
157+
"create-default-disk-labeled-nodes": "true",
158+
}
159+
160+
for setting, expectedValue := range settings {
161+
getSettingCommand := []string{"kubectl", "-n", longhornNamespace, "get", "settings.longhorn.io", setting, `-o=jsonpath='{.value}'`}
162+
settingValue, err := kubectl.Command(l.client, nil, l.cluster.ID, getSettingCommand, "")
163+
require.NoError(l.T(), err)
164+
// The output extracted from kubectl has single quotes and a newline on the end.
165+
require.Equal(l.T(), fmt.Sprintf("'%s'\n", expectedValue), settingValue)
166+
}
167+
168+
l.T().Logf("Create nginx deployment with Longhorn PVC")
169+
nginxResponse := cloudprovider.CreatePVCWorkload(l.T(), l.client, l.cluster.ID, longhornDefaultStorageClassName)
170+
171+
// Fetch the name of the created volume from the nginx response.
172+
nginxSpec := &appv1.DeploymentSpec{}
173+
err = steveV1.ConvertToK8sType(nginxResponse.Spec, nginxSpec)
174+
require.NoError(l.T(), err)
175+
176+
// Even though the Longhorn default for number of replicas is 2, Rancher enforces its own default of 3.
177+
volumeName := nginxSpec.Template.Spec.Volumes[0].Name
178+
checkReplicasCommand := []string{"kubectl", "-n", longhornNamespace, "get", "volumes.longhorn.io", volumeName, `-o=jsonpath="{.spec.numberOfReplicas}"`}
179+
settingValue, err := kubectl.Command(l.client, nil, l.cluster.ID, checkReplicasCommand, "")
180+
require.NoError(l.T(), err)
181+
require.Equal(l.T(), "\"3\"\n", settingValue)
182+
183+
// Create a new namespace and a debug pod within it to check the host filesystem for the custom Longhorn data directory.
184+
// We do this in a separate namespace to ease cleanup.
185+
l.T().Logf("Create namespace [%v] to check node filesystem with debugger pod", debugNamespace)
186+
createdNamespace, err := namespace_actions.CreateNamespace(l.client, debugNamespace, "{}", nil, nil, l.project)
187+
require.NoError(l.T(), err)
188+
189+
steveClient, err := l.client.Steve.ProxyDownstream(l.cluster.ID)
190+
require.NoError(l.T(), err)
191+
192+
l.session.RegisterCleanupFunc(func() error {
193+
return steveClient.SteveType(namespace_actions.NamespaceSteveType).Delete(createdNamespace)
194+
})
195+
196+
checkDataPathCommand := []string{"kubectl", "debug", "node/" + workerName, "-n", debugNamespace, "--profile=general", "--image=busybox", "--", "/bin/sh", "-c", "test -d /host/var/lib/longhorn-custom/replicas && test -f /host/var/lib/longhorn-custom/longhorn-disk.cfg"}
197+
_, err = kubectl.Command(l.client, nil, l.cluster.ID, checkDataPathCommand, "")
198+
require.NoError(l.T(), err)
199+
200+
waitForPodCommand := []string{"kubectl", "wait", "--for=jsonpath='{.status.phase}'=Succeeded", "-n", debugNamespace, "pod", "--all"}
201+
_, err = kubectl.Command(l.client, nil, l.cluster.ID, waitForPodCommand, "")
202+
require.NoError(l.T(), err)
203+
204+
debugPods, err := steveClient.SteveType(pods.PodResourceSteveType).NamespacedSteveClient(debugNamespace).List(nil)
205+
require.NoError(l.T(), err)
206+
207+
podStatus := &corev1.PodStatus{}
208+
err = steveV1.ConvertToK8sType(debugPods.Data[0].Status, podStatus)
209+
require.NoError(l.T(), err)
210+
require.Equal(l.T(), "Succeeded", string(podStatus.Phase))
211+
}
212+
213+
func (l *LonghornTestSuite) TestVolumeCreationOnWorkload() {
214+
chart, err := shepherd_charts.GetChartStatus(l.client, l.cluster.ID, longhornNamespace, longhornChartName)
215+
require.NoError(l.T(), err)
216+
217+
if !chart.IsAlreadyInstalled {
218+
l.T().Logf("Installing Lonhgorn chart in cluster [%v] with latest version [%v] in project [%v] and namespace [%v]", l.cluster.Name, l.payloadOpts.Version, l.project.Name, l.payloadOpts.Namespace)
219+
err = charts.InstallLonghornChart(l.client, l.payloadOpts, nil)
220+
require.NoError(l.T(), err)
221+
}
222+
223+
l.T().Logf("Create nginx deployment with Longhorn PVC")
224+
nginxResponse := cloudprovider.CreatePVCWorkload(l.T(), l.client, l.cluster.ID, longhornDefaultStorageClassName)
225+
226+
err = shepherd_charts.WatchAndWaitDeployments(l.client, l.cluster.ID, namespaces.Default, metav1.ListOptions{})
227+
require.NoError(l.T(), err)
228+
229+
kubeConfig, err := kubeconfig.GetKubeconfig(l.client, l.cluster.ID)
230+
require.NoError(l.T(), err)
231+
232+
var restConfig *rest.Config
233+
restConfig, err = (*kubeConfig).ClientConfig()
234+
require.NoError(l.T(), err)
235+
236+
steveClient, err := l.client.Steve.ProxyDownstream(l.cluster.ID)
237+
require.NoError(l.T(), err)
238+
239+
pods, err := steveClient.SteveType(pods.PodResourceSteveType).NamespacedSteveClient(namespaces.Default).List(nil)
240+
require.NoError(l.T(), err)
241+
242+
var podName string
243+
for _, pod := range pods.Data {
244+
if strings.Contains(pod.Name, nginxResponse.ObjectMeta.Name) {
245+
podName = pod.Name
246+
break
247+
}
248+
}
249+
250+
l.T().Logf("Write to mounted volume on pod [%v]", podName)
251+
writeToMountedVolume := []string{"touch", "/auto-mnt/test-file"}
252+
_, err = kubeconfig.KubectlExec(restConfig, podName, namespaces.Default, writeToMountedVolume)
253+
require.NoError(l.T(), err)
254+
255+
checkFileOnVolume := []string{"stat", "/auto-mnt/test-file"}
256+
_, err = kubeconfig.KubectlExec(restConfig, podName, namespaces.Default, checkFileOnVolume)
257+
require.NoError(l.T(), err)
258+
}
259+
260+
// In order for 'go test' to run this suite, we need to create
261+
// a normal test function and pass our suite to suite.Run
262+
func TestLonghornTestSuite(t *testing.T) {
263+
suite.Run(t, new(LonghornTestSuite))
264+
}

0 commit comments

Comments
 (0)