Repository: longhorn/longhorn-manager
Pull request #3617: feat(metric): collect system host arch and volume encrypted count
Author: login: c3y1huang
Created: 2025-03-06T04:40:48Z | Merged: 2025-03-06T08:50:47Z

#### Which issue(s) this PR fixes:
<!--
Use `Issue #<issue number>` or `Issue longhorn/longhorn#<issue number>` or `Issue (paste link of issue)`. DON'T use `Fixes #<issue number>` or `Fixes (paste link of issue)`, as it will automatically close the linked issue when the PR is merged.
-->
Issue longhorn/longhorn#7047

#### What this PR does / why we need it:

Gather host architecture and the count of encrypted volumes to the upgrade responder.

#### Special notes for your reviewer:

`None`

#### Additional documentation or context

`None`



Diff:
diff --git a/controller/setting_controller.go b/controller/setting_controller.go
index 07eea1593f..4176d6b61d 100644
--- a/controller/setting_controller.go
+++ b/controller/setting_controller.go
@@ -1391,6 +1391,7 @@ const (
 	ClusterInfoVolumeAccessModeCountFmt                  = "LonghornVolumeAccessMode%sCount"
 	ClusterInfoVolumeDataEngineCountFmt                  = "LonghornVolumeDataEngine%sCount"
 	ClusterInfoVolumeDataLocalityCountFmt                = "LonghornVolumeDataLocality%sCount"
+	ClusterInfoVolumeEncryptedCountFmt                   = "LonghornVolumeEncrypted%sCount"
 	ClusterInfoVolumeFrontendCountFmt                    = "LonghornVolumeFrontend%sCount"
 	ClusterInfoVolumeReplicaAutoBalanceCountFmt          = "LonghornVolumeReplicaAutoBalance%sCount"
 	ClusterInfoVolumeReplicaSoftAntiAffinityCountFmt     = "LonghornVolumeReplicaSoftAntiAffinity%sCount"
@@ -1407,6 +1408,7 @@ const (
 	ClusterInfoKubernetesVersion      = util.StructName("KubernetesVersion")
 	ClusterInfoKubernetesNodeProvider = util.StructName("KubernetesNodeProvider")
 
+	ClusterInfoHostArch          = util.StructName("HostArch")
 	ClusterInfoHostKernelRelease = util.StructName("HostKernelRelease")
 	ClusterInfoHostOsDistro      = util.StructName("HostOsDistro")
 
@@ -1677,6 +1679,7 @@ func (info *ClusterInfo) collectVolumesInfo() error {
 	accessModeCountStruct := newStruct()
 	dataEngineCountStruct := newStruct()
 	dataLocalityCountStruct := newStruct()
+	encryptedCountStruct := newStruct()
 	frontendCountStruct := newStruct()
 	replicaAutoBalanceCountStruct := newStruct()
 	replicaSoftAntiAffinityCountStruct := newStruct()
@@ -1714,6 +1717,9 @@ func (info *ClusterInfo) collectVolumesInfo() error {
 		}
 		dataLocalityCountStruct[util.StructName(fmt.Sprintf(ClusterInfoVolumeDataLocalityCountFmt, dataLocality))]++
 
+		encrypted := util.ConvertToCamel(strconv.FormatBool(volume.Spec.Encrypted), "-")
+		encryptedCountStruct[util.StructName(fmt.Sprintf(ClusterInfoVolumeEncryptedCountFmt, encrypted))]++
+
 		if volume.Spec.Frontend != "" && !isVolumeUsingV2DataEngine {
 			frontend := util.ConvertToCamel(string(volume.Spec.Frontend), "-")
 			frontendCountStruct[util.StructName(fmt.Sprintf(ClusterInfoVolumeFrontendCountFmt, frontend))]++
@@ -1746,6 +1752,7 @@ func (info *ClusterInfo) collectVolumesInfo() error {
 	info.structFields.fields.AppendCounted(accessModeCountStruct)
 	info.structFields.fields.AppendCounted(dataEngineCountStruct)
 	info.structFields.fields.AppendCounted(dataLocalityCountStruct)
+	info.structFields.fields.AppendCounted(encryptedCountStruct)
 	info.structFields.fields.AppendCounted(frontendCountStruct)
 	info.structFields.fields.AppendCounted(replicaAutoBalanceCountStruct)
 	info.structFields.fields.AppendCounted(replicaSoftAntiAffinityCountStruct)
@@ -1799,6 +1806,10 @@ func (info *ClusterInfo) collectSettingInVolume(volumeSpecValue, ignoredValue st
 }
 
 func (info *ClusterInfo) collectNodeScope() {
+	if err := info.collectHostArch(); err != nil {
+		info.logger.WithError(err).Warn("Failed to collect host architecture")
+	}
+
 	if err := info.collectHostKernelRelease(); err != nil {
 		info.logger.WithError(err).Warn("Failed to collect host kernel release")
 	}
@@ -1816,6 +1827,14 @@ func (info *ClusterInfo) collectNodeScope() {
 	}
 }
 
+func (info *ClusterInfo) collectHostArch() error {
+	arch, err := lhns.GetArch()
+	if err == nil {
+		info.structFields.tags.Append(ClusterInfoHostArch, arch)
+	}
+	return err
+}
+
 func (info *ClusterInfo) collectHostKernelRelease() error {
 	kernelRelease, err := lhns.GetKernelRelease()
 	if err == nil {
diff --git a/go.mod b/go.mod
index 2735cbb044..26f9356b04 100644
--- a/go.mod
+++ b/go.mod
@@ -64,7 +64,7 @@ require (
 	github.com/kubernetes-csi/csi-lib-utils v0.21.0
 	github.com/longhorn/backing-image-manager v1.8.0
 	github.com/longhorn/backupstore v0.0.0-20250227220202-651bd33886fe
-	github.com/longhorn/go-common-libs v0.0.0-20250215052214-151615b29f8e
+	github.com/longhorn/go-common-libs v0.0.0-20250306071949-f040e0263527
 	github.com/longhorn/go-iscsi-helper v0.0.0-20250111093313-7e1930499625
 	github.com/longhorn/go-spdk-helper v0.0.0-20250227072252-dae1360f31f6
 	github.com/longhorn/longhorn-engine v1.8.0
@@ -119,7 +119,7 @@ require (
 	github.com/x448/float16 v0.8.4 // indirect
 	github.com/yusufpapurcu/wmi v1.2.4 // indirect
 	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
-	golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect
+	golang.org/x/exp v0.0.0-20250228200357-dead58393ab7 // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 // indirect
 	gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
@@ -221,6 +221,6 @@ require (
 	k8s.io/kubelet v0.0.0 // indirect
 	sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect
 	sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
-	sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect
+	sigs.k8s.io/structured-merge-diff/v4 v4.5.0
 	sigs.k8s.io/yaml v1.4.0 // indirect
 )
diff --git a/go.sum b/go.sum
index 73867a8b93..b09ed71f18 100644
--- a/go.sum
+++ b/go.sum
@@ -154,8 +154,8 @@ github.com/longhorn/backing-image-manager v1.8.0 h1:/ydwZyoaPyhXnB7Oo4ym/c486m0k
 github.com/longhorn/backing-image-manager v1.8.0/go.mod h1:tXKCKywEAnPCVhpccXzO9o7EV8LufbeMSxKU2sWwu54=
 github.com/longhorn/backupstore v0.0.0-20250227220202-651bd33886fe h1:94FXqrJeaXrwuQNk4GLz0SSaMKT90Zcu1Uhu0qVb5EM=
 github.com/longhorn/backupstore v0.0.0-20250227220202-651bd33886fe/go.mod h1:68UurOCxyLbFS/490FPo+q2G/7iJXfPwI33eNm1m3D8=
-github.com/longhorn/go-common-libs v0.0.0-20250215052214-151615b29f8e h1:QxJaa0Jo3yGWXrxk317mH1KJwgz7SwDldH1ec/yNEss=
-github.com/longhorn/go-common-libs v0.0.0-20250215052214-151615b29f8e/go.mod h1:qDoXeH6jrSofJyr3ubwwhlptIKHHsQwTsm5TpNTs1B0=
+github.com/longhorn/go-common-libs v0.0.0-20250306071949-f040e0263527 h1:Kvl+E2Wmionx8NW8gvrlEkfiNZUr8q+KNtYORlCiOQc=
+github.com/longhorn/go-common-libs v0.0.0-20250306071949-f040e0263527/go.mod h1:mcl7/l12vDFOchB2LYHpN27+QDGuFhvMpJvmNlrpl9k=
 github.com/longhorn/go-iscsi-helper v0.0.0-20250111093313-7e1930499625 h1:d39A3041RyFve26tIuKUuzrh2CkBY970xlGIXgMA998=
 github.com/longhorn/go-iscsi-helper v0.0.0-20250111093313-7e1930499625/go.mod h1:yIm3sGRuYOw/Y3XzRhG5+3FlZBOfrU5EZOavzwL2jVs=
 github.com/longhorn/go-spdk-helper v0.0.0-20250227072252-dae1360f31f6 h1:Q+FjEjs3asrIvMbgEHw9kMcyhm5lk4+MDJR9XHG3SP8=
@@ -335,8 +335,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
 golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
-golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs=
-golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo=
+golang.org/x/exp v0.0.0-20250228200357-dead58393ab7 h1:aWwlzYV971S4BXRS9AmqwDLAD85ouC6X+pocatKY58c=
+golang.org/x/exp v0.0.0-20250228200357-dead58393ab7/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
diff --git a/vendor/github.com/longhorn/go-common-libs/ns/sys.go b/vendor/github.com/longhorn/go-common-libs/ns/sys.go
index ff8b561687..22edf36a3e 100644
--- a/vendor/github.com/longhorn/go-common-libs/ns/sys.go
+++ b/vendor/github.com/longhorn/go-common-libs/ns/sys.go
@@ -10,6 +10,31 @@ import (
 	"github.com/longhorn/go-common-libs/types"
 )
 
+// GetArch switches to the host namespace and retrieves the system architecture.
+func GetArch() (string, error) {
+	var err error
+	defer func() {
+		err = errors.Wrap(err, "failed to get system architecture")
+	}()
+
+	fn := func() (interface{}, error) {
+		return sys.GetArch()
+	}
+
+	rawResult, err := RunFunc(fn, 0)
+	if err != nil {
+		return "", err
+	}
+
+	var result string
+	var ableToCast bool
+	result, ableToCast = rawResult.(string)
+	if !ableToCast {
+		return "", errors.Errorf(types.ErrNamespaceCastResultFmt, result, rawResult)
+	}
+	return result, nil
+}
+
 // GetKernelRelease switches to the host namespace and retrieves the kernel release.
 func GetKernelRelease() (string, error) {
 	var err error
diff --git a/vendor/github.com/longhorn/go-common-libs/sys/sys.go b/vendor/github.com/longhorn/go-common-libs/sys/sys.go
index 576ae86d0b..884aabe6b0 100644
--- a/vendor/github.com/longhorn/go-common-libs/sys/sys.go
+++ b/vendor/github.com/longhorn/go-common-libs/sys/sys.go
@@ -19,7 +19,31 @@ import (
 	commonio "github.com/longhorn/go-common-libs/io"
 )
 
-// GetKernelRelease returns the kernel release string.
+// GetArch retrieves the system architecture by calling the unix.Uname function
+// and extracting the architecture information from the Utsname structure.
+// It returns the architecture as a string and an error if the operation fails.
+func GetArch() (string, error) {
+	utsname := &unix.Utsname{}
+	if err := unix.Uname(utsname); err != nil {
+		logrus.WithError(err).Warn("Failed to get system architecture")
+		return "", err
+	}
+
+	// Extract the architecture from the Utsname structure
+	arch := make([]byte, 0, len(utsname.Machine))
+	for _, b := range utsname.Machine {
+		if b == 0x00 {
+			logrus.Trace("Found end of architecture string [0x00]")
+			break
+		}
+		arch = append(arch, byte(b))
+	}
+	return string(arch), nil
+}
+
+// GetKernelRelease retrieves the kernel release by calling the unix.Uname function
+// and extracting the release information from the Utsname structure.
+// It returns the kernel release as a string and an error if the operation fails.
 func GetKernelRelease() (string, error) {
 	utsname := &unix.Utsname{}
 	if err := unix.Uname(utsname); err != nil {
diff --git a/vendor/golang.org/x/exp/constraints/constraints.go b/vendor/golang.org/x/exp/constraints/constraints.go
index a9392af7c1..9d260bab19 100644
--- a/vendor/golang.org/x/exp/constraints/constraints.go
+++ b/vendor/golang.org/x/exp/constraints/constraints.go
@@ -6,6 +6,8 @@
 // with type parameters.
 package constraints
 
+import "cmp"
+
 // Signed is a constraint that permits any signed integer type.
 // If future releases of Go add new predeclared signed integer types,
 // this constraint will be modified to include them.
@@ -47,6 +49,6 @@ type Complex interface {
 // this constraint will be modified to include them.
 //
 // This type is redundant since Go 1.21 introduced [cmp.Ordered].
-type Ordered interface {
-	Integer | Float | ~string
-}
+//
+//go:fix inline
+type Ordered = cmp.Ordered
diff --git a/vendor/golang.org/x/exp/slices/slices.go b/vendor/golang.org/x/exp/slices/slices.go
index 757383ea1c..da0df370da 100644
--- a/vendor/golang.org/x/exp/slices/slices.go
+++ b/vendor/golang.org/x/exp/slices/slices.go
@@ -10,16 +10,13 @@ import (
 	"slices"
 )
 
-// TODO(adonovan): when https://go.dev/issue/32816 is accepted, all of
-// these functions should be annotated (provisionally with "//go:fix
-// inline") so that tools can safely and automatically replace calls
-// to exp/slices with calls to std slices by inlining them.
-
 // Equal reports whether two slices are equal: the same length and all
 // elements equal. If the lengths are different, Equal returns false.
 // Otherwise, the elements are compared in increasing index order, and the
 // comparison stops at the first unequal pair.
 // Floating point NaNs are not considered equal.
+//
+//go:fix inline
 func Equal[S ~[]E, E comparable](s1, s2 S) bool {
 	return slices.Equal(s1, s2)
 }
@@ -29,6 +26,8 @@ func Equal[S ~[]E, E comparable](s1, s2 S) bool {
 // EqualFunc returns false. Otherwise, the elements are compared in
 // increasing index order, and the comparison stops at the first index
 // for which eq returns false.
+//
+//go:fix inline
 func EqualFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, eq func(E1, E2) bool) bool {
 	return slices.EqualFunc(s1, s2, eq)
 }
@@ -40,6 +39,8 @@ func EqualFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, eq func(E1, E2) boo
 // If both slices are equal until one of them ends, the shorter slice is
 // considered less than the longer one.
 // The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2.
+//
+//go:fix inline
 func Compare[S ~[]E, E cmp.Ordered](s1, s2 S) int {
 	return slices.Compare(s1, s2)
 }
@@ -49,29 +50,39 @@ func Compare[S ~[]E, E cmp.Ordered](s1, s2 S) int {
 // The result is the first non-zero result of cmp; if cmp always
 // returns 0 the result is 0 if len(s1) == len(s2), -1 if len(s1) < len(s2),
 // and +1 if len(s1) > len(s2).
+//
+//go:fix inline
 func CompareFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, cmp func(E1, E2) int) int {
 	return slices.CompareFunc(s1, s2, cmp)
 }
 
 // Index returns the index of the first occurrence of v in s,
 // or -1 if not present.
+//
+//go:fix inline
 func Index[S ~[]E, E comparable](s S, v E) int {
 	return slices.Index(s, v)
 }
 
 // IndexFunc returns the first index i satisfying f(s[i]),
 // or -1 if none do.
+//
+//go:fix inline
 func IndexFunc[S ~[]E, E any](s S, f func(E) bool) int {
 	return slices.IndexFunc(s, f)
 }
 
 // Contains reports whether v is present in s.
+//
+//go:fix inline
 func Contains[S ~[]E, E comparable](s S, v E) bool {
 	return slices.Contains(s, v)
 }
 
 // ContainsFunc reports whether at least one
 // element e of s satisfies f(e).
+//
+//go:fix inline
 func ContainsFunc[S ~[]E, E any](s S, f func(E) bool) bool {
 	return slices.ContainsFunc(s, f)
 }
@@ -83,6 +94,8 @@ func ContainsFunc[S ~[]E, E any](s S, f func(E) bool) bool {
 // and r[i+len(v)] == value originally at r[i].
 // Insert panics if i is out of range.
 // This function is O(len(s) + len(v)).
+//
+//go:fix inline
 func Insert[S ~[]E, E any](s S, i int, v ...E) S {
 	return slices.Insert(s, i, v...)
 }
@@ -92,6 +105,8 @@ func Insert[S ~[]E, E any](s S, i int, v ...E) S {
 // Delete is O(len(s)-i), so if many items must be deleted, it is better to
 // make a single call deleting them all together than to delete one at a time.
 // Delete zeroes the elements s[len(s)-(j-i):len(s)].
+//
+//go:fix inline
 func Delete[S ~[]E, E any](s S, i, j int) S {
 	return slices.Delete(s, i, j)
 }
@@ -99,6 +114,8 @@ func Delete[S ~[]E, E any](s S, i, j int) S {
 // DeleteFunc removes any elements from s for which del returns true,
 // returning the modified slice.
 // DeleteFunc zeroes the elements between the new length and the original length.
+//
+//go:fix inline
 func DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S {
 	return slices.DeleteFunc(s, del)
 }
@@ -106,12 +123,16 @@ func DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S {
 // Replace replaces the elements s[i:j] by the given v, and returns the
 // modified slice. Replace panics if s[i:j] is not a valid slice of s.
 // When len(v) < (j-i), Replace zeroes the elements between the new length and the original length.
+//
+//go:fix inline
 func Replace[S ~[]E, E any](s S, i, j int, v ...E) S {
 	return slices.Replace(s, i, j, v...)
 }
 
 // Clone returns a copy of the slice.
 // The elements are copied using assignment, so this is a shallow clone.
+//
+//go:fix inline
 func Clone[S ~[]E, E any](s S) S {
 	return slices.Clone(s)
 }
@@ -121,6 +142,8 @@ func Clone[S ~[]E, E any](s S) S {
 // Compact modifies the contents of the slice s and returns the modified slice,
 // which may have a smaller length.
 // Compact zeroes the elements between the new length and the original length.
+//
+//go:fix inline
 func Compact[S ~[]E, E comparable](s S) S {
 	return slices.Compact(s)
 }
@@ -128,6 +151,8 @@ func Compact[S ~[]E, E comparable](s S) S {
 // CompactFunc is like [Compact] but uses an equality function to compare elements.
 // For runs of elements that compare equal, CompactFunc keeps the first one.
 // CompactFunc zeroes the elements between the new length and the original length.
+//
+//go:fix inline
 func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S {
 	return slices.CompactFunc(s, eq)
 }
@@ -136,16 +161,22 @@ func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S {
 // another n elements. After Grow(n), at least n elements can be appended
 // to the slice without another allocation. If n is negative or too large to
 // allocate the memory, Grow panics.
+//
+//go:fix inline
 func Grow[S ~[]E, E any](s S, n int) S {
 	return slices.Grow(s, n)
 }
 
 // Clip removes unused capacity from the slice, returning s[:len(s):len(s)].
+//
+//go:fix inline
 func Clip[S ~[]E, E any](s S) S {
 	return slices.Clip(s)
 }
 
 // Reverse reverses the elements of the slice in place.
+//
+//go:fix inline
 func Reverse[S ~[]E, E any](s S) {
 	slices.Reverse(s)
 }
diff --git a/vendor/golang.org/x/exp/slices/sort.go b/vendor/golang.org/x/exp/slices/sort.go
index e270a74652..bd91a8d402 100644
--- a/vendor/golang.org/x/exp/slices/sort.go
+++ b/vendor/golang.org/x/exp/slices/sort.go
@@ -9,11 +9,10 @@ import (
 	"slices"
 )
 
-// TODO(adonovan): add a "//go:fix inline" annotation to each function
-// in this file; see https://go.dev/issue/32816.
-
 // Sort sorts a slice of any ordered type in ascending order.
 // When sorting floating-point numbers, NaNs are ordered before other values.
+//
+//go:fix inline
 func Sort[S ~[]E, E cmp.Ordered](x S) {
 	slices.Sort(x)
 }
@@ -27,23 +26,31 @@ func Sort[S ~[]E, E cmp.Ordered](x S) {
 // SortFunc requires that cmp is a strict weak ordering.
 // See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings.
 // To indicate 'uncomparable', return 0 from the function.
+//
+//go:fix inline
 func SortFunc[S ~[]E, E any](x S, cmp func(a, b E) int) {
 	slices.SortFunc(x, cmp)
 }
 
 // SortStableFunc sorts the slice x while keeping the original order of equal
 // elements, using cmp to compare elements in the same way as [SortFunc].
+//
+//go:fix inline
 func SortStableFunc[S ~[]E, E any](x S, cmp func(a, b E) int) {
 	slices.SortStableFunc(x, cmp)
 }
 
 // IsSorted reports whether x is sorted in ascending order.
+//
+//go:fix inline
 func IsSorted[S ~[]E, E cmp.Ordered](x S) bool {
 	return slices.IsSorted(x)
 }
 
 // IsSortedFunc reports whether x is sorted in ascending order, with cmp as the
 // comparison function as defined by [SortFunc].
+//
+//go:fix inline
 func IsSortedFunc[S ~[]E, E any](x S, cmp func(a, b E) int) bool {
 	return slices.IsSortedFunc(x, cmp)
 }
@@ -51,6 +58,8 @@ func IsSortedFunc[S ~[]E, E any](x S, cmp func(a, b E) int) bool {
 // Min returns the minimal value in x. It panics if x is empty.
 // For floating-point numbers, Min propagates NaNs (any NaN value in x
 // forces the output to be NaN).
+//
+//go:fix inline
 func Min[S ~[]E, E cmp.Ordered](x S) E {
 	return slices.Min(x)
 }
@@ -58,6 +67,8 @@ func Min[S ~[]E, E cmp.Ordered](x S) E {
 // MinFunc returns the minimal value in x, using cmp to compare elements.
 // It panics if x is empty. If there is more than one minimal element
 // according to the cmp function, MinFunc returns the first one.
+//
+//go:fix inline
 func MinFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E {
 	return slices.MinFunc(x, cmp)
 }
@@ -65,6 +76,8 @@ func MinFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E {
 // Max returns the maximal value in x. It panics if x is empty.
 // For floating-point E, Max propagates NaNs (any NaN value in x
 // forces the output to be NaN).
+//
+//go:fix inline
 func Max[S ~[]E, E cmp.Ordered](x S) E {
 	return slices.Max(x)
 }
@@ -72,6 +85,8 @@ func Max[S ~[]E, E cmp.Ordered](x S) E {
 // MaxFunc returns the maximal value in x, using cmp to compare elements.
 // It panics if x is empty. If there is more than one maximal element
 // according to the cmp function, MaxFunc returns the first one.
+//
+//go:fix inline
 func MaxFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E {
 	return slices.MaxFunc(x, cmp)
 }
@@ -80,6 +95,8 @@ func MaxFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E {
 // where target is found, or the position where target would appear in the
 // sort order; it also returns a bool saying whether the target is really found
 // in the slice. The slice must be sorted in increasing order.
+//
+//go:fix inline
 func BinarySearch[S ~[]E, E cmp.Ordered](x S, target E) (int, bool) {
 	return slices.BinarySearch(x, target)
 }
@@ -91,6 +108,8 @@ func BinarySearch[S ~[]E, E cmp.Ordered](x S, target E) (int, bool) {
 // or a positive number if the slice element follows the target.
 // cmp must implement the same ordering as the slice, such that if
 // cmp(a, t) < 0 and cmp(b, t) >= 0, then a must precede b in the slice.
+//
+//go:fix inline
 func BinarySearchFunc[S ~[]E, E, T any](x S, target T, cmp func(E, T) int) (int, bool) {
 	return slices.BinarySearchFunc(x, target, cmp)
 }
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 30afef9581..68c6895859 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -237,8 +237,8 @@ github.com/longhorn/backupstore/logging
 github.com/longhorn/backupstore/systembackup
 github.com/longhorn/backupstore/types
 github.com/longhorn/backupstore/util
-# github.com/longhorn/go-common-libs v0.0.0-20250215052214-151615b29f8e
-## explicit; go 1.22.7
+# github.com/longhorn/go-common-libs v0.0.0-20250306071949-f040e0263527
+## explicit; go 1.23.0
 github.com/longhorn/go-common-libs/backup
 github.com/longhorn/go-common-libs/exec
 github.com/longhorn/go-common-libs/io
@@ -585,8 +585,8 @@ golang.org/x/crypto/internal/alias
 golang.org/x/crypto/internal/poly1305
 golang.org/x/crypto/nacl/secretbox
 golang.org/x/crypto/salsa20/salsa
-# golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac
-## explicit; go 1.22.0
+# golang.org/x/exp v0.0.0-20250228200357-dead58393ab7
+## explicit; go 1.23.0
 golang.org/x/exp/constraints
 golang.org/x/exp/slices
 # golang.org/x/mod v0.23.0


Reviews:
user: login: c3y1huang
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T06:35:36Z
user: login: derekbit
body: LGTM. Will merged after updating deps.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T07:22:04Z
user: login: derekbit
body: Thank you.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-06T08:50:41Z

Review comments:
diff_hunk: @@ -4,6 +4,8 @@ go 1.23.1
 
 toolchain go1.24.0
 
+replace github.com/longhorn/go-common-libs => github.com/c3y1huang/go-common-libs v0.0.0-20250306062107-90166de73d8a
path: go.mod
user: login: c3y1huang
body: TODO: update after dependent PR merged.
created_at: 2025-03-06T06:35:36Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-03-06T04:40:55Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This pull request updates `controller/setting_controller.go` by introducing functionality to track encrypted volume counts and collect host architecture information. It adds two new constants for encryption count formatting and identifying host architecture, extends the logic in `collectVolumesInfo` to tally encrypted volumes, and implements a new method `collectHostArch` that obtains architecture details via `lhns.GetArch()`. The new method is integrated into `collectNodeScope` with appropriate error handling.

## Changes

| File                                  | Change Summary                                                                                                                                                                                                                                                     |
|---------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `controller/setting_controller.go`    | - Added constant `ClusterInfoVolumeEncryptedCountFmt` for formatting encrypted volume counts.<br>- Added constant `ClusterInfoHostArch` to represent host architecture.<br>- Updated `collectVolumesInfo` to count encrypted volumes.<br>- Added `collectHostArch` to obtain and log host architecture info and integrated it into `collectNodeScope`. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant C as Controller
    participant CI as ClusterInfo
    participant LHNS as LHNS

    C->>CI: collectNodeScope()
    CI->>CI: collectHostArch()
    CI->>LHNS: GetArch()
    LHNS-->>CI: Return architecture info / error
    CI-->>C: Append host architecture to tags (or log warning on error)
```

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Free**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 540a41d0c2be1b41e6c572a49d13703d3fa32429 and 734661fdf3c643f09070eaf4a5ffa64adb225b9a.

</details>

<details>
<summary>⛔ Files ignored due to path filters (8)</summary>

* `go.mod` is excluded by `!go.mod`
* `go.sum` is excluded by `!**/*.sum`, `!go.sum`
* `vendor/github.com/longhorn/go-common-libs/ns/sys.go` is excluded by `!vendor/**`
* `vendor/github.com/longhorn/go-common-libs/sys/sys.go` is excluded by `!vendor/**`
* `vendor/golang.org/x/exp/constraints/constraints.go` is excluded by `!vendor/**`
* `vendor/golang.org/x/exp/slices/slices.go` is excluded by `!vendor/**`
* `vendor/golang.org/x/exp/slices/sort.go` is excluded by `!vendor/**`
* `vendor/modules.txt` is excluded by `!vendor/**`

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `controller/setting_controller.go` (7 hunks)

</details>

</details>
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- tips_start -->

---

> [!NOTE]
> <details>
> <summary>🎁 Summarized by CodeRabbit Free</summary>
> 
> Your organization has reached its limit of developer seats under the Pro Plan. For new users, CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please add seats to your subscription by visiting <https://app.coderabbit.ai/login>.If you believe this is a mistake and have available seats, please assign one to the pull request author through the subscription management page using the link above.
> 
> </details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=longhorn/longhorn-manager&utm_content=3617):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: c3y1huang
created_at: 2025-03-06T04:42:08Z
body: @coderabbitai review
author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-03-06T04:42:13Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>:white_check_mark: Actions performed</summary>

Review triggered.

> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

</details>
author_association: NONE