Skip to content

Commit 46ca2d3

Browse files
committed
Separate airgap storage space preflight for controller and worker nodes
Signed-off-by: Evans Mungai <evans@replicated.com>
1 parent 473f5e1 commit 46ca2d3

File tree

9 files changed

+177
-13
lines changed

9 files changed

+177
-13
lines changed

api/controllers/install/hostpreflight.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ func (c *InstallController) RunHostPreflights(ctx context.Context, opts RunHostP
2323
if err != nil {
2424
return fmt.Errorf("failed to get airgap info: %w", err)
2525
}
26-
// Controller nodes require 2x the extracted bundle size for processing
27-
controllerAirgapStorageSpace = preflights.CalculateControllerAirgapStorageSpace(airgapInfo.Spec.UncompressedSize)
26+
controllerAirgapStorageSpace = preflights.CalculateAirgapStorageSpace(airgapInfo.Spec.UncompressedSize, true)
2827
}
2928

3029
// Prepare host preflights

api/internal/managers/preflight/hostpreflight.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type PrepareHostPreflightOptions struct {
2424
IsJoin bool
2525
IsUI bool
2626
ControllerAirgapStorageSpace string
27+
WorkerAirgapStorageSpace string
2728
}
2829

2930
type RunHostPreflightOptions struct {
@@ -94,6 +95,7 @@ func (m *hostPreflightManager) prepareHostPreflights(ctx context.Context, rc run
9495
IsJoin: opts.IsJoin,
9596
IsUI: opts.IsUI,
9697
ControllerAirgapStorageSpace: opts.ControllerAirgapStorageSpace,
98+
WorkerAirgapStorageSpace: opts.WorkerAirgapStorageSpace,
9799
}
98100
if cidr := rc.GlobalCIDR(); cidr != "" {
99101
prepareOpts.GlobalCIDR = &cidr

cmd/installer/cli/install_runpreflights.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ func runInstallPreflights(ctx context.Context, flags InstallCmdFlags, rc runtime
112112
if err != nil {
113113
return fmt.Errorf("failed to get airgap info: %w", err)
114114
}
115-
// Controller nodes require 2x the extracted bundle size for processing
116-
controllerAirgapStorageSpace = preflights.CalculateControllerAirgapStorageSpace(airgapInfo.Spec.UncompressedSize)
115+
// The first installed node is always a controller
116+
controllerAirgapStorageSpace = preflights.CalculateAirgapStorageSpace(airgapInfo.Spec.UncompressedSize, true)
117117
}
118118

119119
opts := preflights.PrepareOptions{

cmd/installer/cli/join_runpreflights.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"strings"
78

89
"github.com/replicatedhq/embedded-cluster/kinds/types/join"
910
newconfig "github.com/replicatedhq/embedded-cluster/pkg-new/config"
@@ -103,11 +104,19 @@ func runJoinPreflights(ctx context.Context, jcmd *join.JoinCommandResponse, flag
103104

104105
domains := runtimeconfig.GetDomains(jcmd.InstallationSpec.Config)
105106

106-
// Calculate airgap storage space requirement (2x uncompressed size for controller nodes)
107+
// Calculate airgap storage space requirement based on node type
107108
var controllerAirgapStorageSpace string
109+
var workerAirgapStorageSpace string
108110
if jcmd.InstallationSpec.AirGap && jcmd.InstallationSpec.AirgapUncompressedSize > 0 {
109-
// Controller nodes require 2x the extracted bundle size for processing
110-
controllerAirgapStorageSpace = preflights.CalculateControllerAirgapStorageSpace(jcmd.InstallationSpec.AirgapUncompressedSize)
111+
// Determine if this is a controller node by checking the join command
112+
isController := strings.Contains(jcmd.K0sJoinCommand, "controller")
113+
if isController {
114+
logrus.Debug("Node type determined from join command: controller")
115+
controllerAirgapStorageSpace = preflights.CalculateAirgapStorageSpace(jcmd.InstallationSpec.AirgapUncompressedSize, true)
116+
} else {
117+
logrus.Debug("Node type determined from join command: worker")
118+
workerAirgapStorageSpace = preflights.CalculateAirgapStorageSpace(jcmd.InstallationSpec.AirgapUncompressedSize, false)
119+
}
111120
}
112121

113122
hpf, err := preflights.Prepare(ctx, preflights.PrepareOptions{
@@ -127,6 +136,7 @@ func runJoinPreflights(ctx context.Context, jcmd *join.JoinCommandResponse, flag
127136
TCPConnectionsRequired: jcmd.TCPConnectionsRequired,
128137
IsJoin: true,
129138
ControllerAirgapStorageSpace: controllerAirgapStorageSpace,
139+
WorkerAirgapStorageSpace: workerAirgapStorageSpace,
130140
})
131141
if err != nil {
132142
return err

pkg-new/preflights/host-preflight.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,23 @@ spec:
240240
{{- end }}
241241
- pass:
242242
message: The filesystem at {{ .DataDir }} has sufficient available space for airgap bundle processing
243+
- diskUsage:
244+
checkName: Worker Airgap Storage Space
245+
collectorName: embedded-cluster-path-usage
246+
exclude: '{{ eq .WorkerAirgapStorageSpace "" }}'
247+
outcomes:
248+
- fail:
249+
when: 'available < {{ .WorkerAirgapStorageSpace }}'
250+
message: >-
251+
{{ if .IsUI -}}
252+
The filesystem at {{ .DataDir }} has less than {{ .WorkerAirgapStorageSpace }} of available storage space needed to store infrastructure images.
253+
Ensure sufficient space is available, or go back to the Setup page and choose a different data directory.
254+
{{- else -}}
255+
The filesystem at {{ .DataDir }} has less than {{ .WorkerAirgapStorageSpace }} of available storage space needed to store infrastructure images.
256+
Ensure sufficient space is available, or use --data-dir to specify an alternative data directory.
257+
{{- end }}
258+
- pass:
259+
message: The filesystem at {{ .DataDir }} has sufficient available space for worker airgap storage
243260
- textAnalyze:
244261
checkName: Default Route
245262
fileName: host-collectors/run-host/ip-route-table.txt

pkg-new/preflights/prepare.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type PrepareOptions struct {
3535
IsJoin bool
3636
IsUI bool
3737
ControllerAirgapStorageSpace string
38+
WorkerAirgapStorageSpace string
3839
}
3940

4041
// Prepare prepares the host preflights spec by merging provided spec with cluster preflights
@@ -61,6 +62,7 @@ func (p *PreflightsRunner) Prepare(ctx context.Context, opts PrepareOptions) (*v
6162
IsJoin: opts.IsJoin,
6263
IsUI: opts.IsUI,
6364
ControllerAirgapStorageSpace: opts.ControllerAirgapStorageSpace,
65+
WorkerAirgapStorageSpace: opts.WorkerAirgapStorageSpace,
6466
}.WithCIDRData(opts.PodCIDR, opts.ServiceCIDR, opts.GlobalCIDR)
6567

6668
if err != nil {

pkg-new/preflights/template.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,18 @@ func renderTemplate(spec string, data types.TemplateData) (string, error) {
4646
return buf.String(), nil
4747
}
4848

49-
// CalculateControllerAirgapStorageSpace calculates the required airgap storage space for controller nodes.
50-
// It multiplies the uncompressed size by 2, rounds up to the nearest natural number, and returns a string.
51-
// The quantity will be in Gi for sizes >= 1 Gi, or Mi for smaller sizes.
52-
func CalculateControllerAirgapStorageSpace(uncompressedSize int64) string {
49+
// CalculateAirgapStorageSpace calculates required storage space for airgap installations.
50+
// Controller nodes need 2x uncompressed size, worker nodes need 1x. Returns "XGi" or "XMi".
51+
func CalculateAirgapStorageSpace(uncompressedSize int64, isController bool) string {
5352
if uncompressedSize <= 0 {
5453
return ""
5554
}
5655

57-
// Controller nodes require 2x the extracted bundle size for processing
58-
requiredBytes := uncompressedSize * 2
56+
requiredBytes := uncompressedSize
57+
if isController {
58+
// Controller nodes require 2x the extracted bundle size for processing
59+
requiredBytes = uncompressedSize * 2
60+
}
5961

6062
// Convert to Gi if >= 1 Gi, otherwise use Mi
6163
if requiredBytes >= 1024*1024*1024 { // 1 Gi in bytes

pkg-new/preflights/template_test.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,3 +620,134 @@ func TestTemplateTCPConnectionsRequired(t *testing.T) {
620620
})
621621
}
622622
}
623+
624+
func TestCalculateAirgapStorageSpace(t *testing.T) {
625+
tests := []struct {
626+
name string
627+
uncompressedSize int64
628+
isController bool
629+
expected string
630+
}{
631+
{
632+
name: "controller node with 1GB uncompressed size",
633+
uncompressedSize: 1024 * 1024 * 1024, // 1GB
634+
isController: true,
635+
expected: "2Gi", // 2x for controller
636+
},
637+
{
638+
name: "worker node with 1GB uncompressed size",
639+
uncompressedSize: 1024 * 1024 * 1024, // 1GB
640+
isController: false,
641+
expected: "1Gi", // 1x for worker
642+
},
643+
{
644+
name: "controller node with 500MB uncompressed size",
645+
uncompressedSize: 500 * 1024 * 1024, // 500MB
646+
isController: true,
647+
expected: "1Gi", // 2x = 1GB, rounded up
648+
},
649+
{
650+
name: "worker node with 500MB uncompressed size",
651+
uncompressedSize: 500 * 1024 * 1024, // 500MB
652+
isController: false,
653+
expected: "500Mi", // 1x = 500MB
654+
},
655+
{
656+
name: "controller node with 100MB uncompressed size",
657+
uncompressedSize: 100 * 1024 * 1024, // 100MB
658+
isController: true,
659+
expected: "200Mi", // 2x = 200MB
660+
},
661+
{
662+
name: "worker node with 100MB uncompressed size",
663+
uncompressedSize: 100 * 1024 * 1024, // 100MB
664+
isController: false,
665+
expected: "100Mi", // 1x = 100MB
666+
},
667+
{
668+
name: "zero uncompressed size",
669+
uncompressedSize: 0,
670+
isController: true,
671+
expected: "",
672+
},
673+
{
674+
name: "negative uncompressed size",
675+
uncompressedSize: -1,
676+
isController: false,
677+
expected: "",
678+
},
679+
}
680+
681+
for _, tt := range tests {
682+
t.Run(tt.name, func(t *testing.T) {
683+
result := CalculateAirgapStorageSpace(tt.uncompressedSize, tt.isController)
684+
require.Equal(t, tt.expected, result)
685+
})
686+
}
687+
}
688+
689+
func TestTemplateAirgapStorageSpaceChecks(t *testing.T) {
690+
tests := []struct {
691+
name string
692+
controllerAirgapStorageSpace string
693+
workerAirgapStorageSpace string
694+
expectControllerCheck bool
695+
expectWorkerCheck bool
696+
}{
697+
{
698+
name: "controller node check",
699+
controllerAirgapStorageSpace: "2Gi",
700+
workerAirgapStorageSpace: "",
701+
expectControllerCheck: true,
702+
expectWorkerCheck: false,
703+
},
704+
{
705+
name: "worker node check",
706+
controllerAirgapStorageSpace: "",
707+
workerAirgapStorageSpace: "1Gi",
708+
expectControllerCheck: false,
709+
expectWorkerCheck: true,
710+
},
711+
{
712+
name: "no airgap checks",
713+
controllerAirgapStorageSpace: "",
714+
workerAirgapStorageSpace: "",
715+
expectControllerCheck: false,
716+
expectWorkerCheck: false,
717+
},
718+
}
719+
720+
for _, tt := range tests {
721+
t.Run(tt.name, func(t *testing.T) {
722+
data := types.TemplateData{
723+
ControllerAirgapStorageSpace: tt.controllerAirgapStorageSpace,
724+
WorkerAirgapStorageSpace: tt.workerAirgapStorageSpace,
725+
}
726+
727+
hpfs, err := GetClusterHostPreflights(context.Background(), data)
728+
require.NoError(t, err)
729+
require.Len(t, hpfs, 1)
730+
731+
spec := hpfs[0].Spec
732+
specStr, err := json.Marshal(spec)
733+
require.NoError(t, err)
734+
specStrLower := strings.ToLower(string(specStr))
735+
736+
if tt.expectControllerCheck {
737+
require.Contains(t, specStrLower, "airgap storage space")
738+
require.Contains(t, specStrLower, "controller")
739+
require.NotContains(t, specStrLower, "worker airgap storage space")
740+
} else {
741+
require.NotContains(t, specStrLower, "airgap storage space")
742+
}
743+
744+
if tt.expectWorkerCheck {
745+
require.Contains(t, specStrLower, "worker airgap storage space")
746+
require.Contains(t, specStrLower, "infrastructure images")
747+
require.NotContains(t, specStrLower, "airgap storage space")
748+
} else {
749+
require.NotContains(t, specStrLower, "worker airgap storage space")
750+
}
751+
})
752+
}
753+
}

pkg-new/preflights/types/template.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ type TemplateData struct {
3636
IsJoin bool
3737
IsUI bool
3838
ControllerAirgapStorageSpace string
39+
WorkerAirgapStorageSpace string
3940
}
4041

4142
// WithCIDRData sets the respective CIDR properties in the TemplateData struct based on the provided CIDR strings

0 commit comments

Comments
 (0)