Skip to content

Commit

Permalink
allow installation to external disks, configure multipathd and extra …
Browse files Browse the repository at this point in the history
…kernel arguments

fix typo in grub2-editenv

fixing issues for multipath config and kernel arguments

enable multipath in initrd

tidy up and vendor go mod deps

add logic to filter interfaces in use for iscsi session

additional logic to dedup disks and only show one device for multipath'd disks

tweak disk filter logic and make multipathd conditional

tweak comments on disk deduplication logic

fix comment in harv-install describing usage of HARVESTER_ADDITIONAL_KERNEL_ARGUMENTS

rebase upstream changes

remove dracut config files needed to enable multipathd. This is now handled in harvester/os2 directly

(cherry picked from commit f0b21b9)
  • Loading branch information
ibrokethecloud authored and Vicente-Cheng committed Aug 28, 2024
1 parent 8365761 commit c240c65
Show file tree
Hide file tree
Showing 31 changed files with 5,824 additions and 5 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/harvester/harvester-installer
go 1.22.5

require (
github.com/dell/goiscsi v1.9.0
github.com/harvester/go-common v0.0.0-20230718010724-11313421a8f5
github.com/imdario/mergo v0.3.16
github.com/insomniacslk/dhcp v0.0.0-20210827173440-b95caade3eac
Expand Down Expand Up @@ -71,4 +72,5 @@ replace (
k8s.io/api => k8s.io/api v0.24.10
k8s.io/apimachinery => k8s.io/apimachinery v0.24.10
k8s.io/client-go => k8s.io/client-go v0.24.10
k8s.io/kubelet => k8s.io/kubelet v0.24.10
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE=
github.com/dell/goiscsi v1.9.0 h1:VvMHbAO4vk80oc/TAbQPYlxysscCqVBW78GyPoUxgik=
github.com/dell/goiscsi v1.9.0/go.mod h1:NI/W/0O1UrMW2zVdMxy4z395Jn0r7utH6RQDFSZiFyQ=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/docker/cli v20.10.20+incompatible h1:lWQbHSHUFs7KraSN2jOJK7zbMS2jNCHI4mt4xUFUVQ4=
Expand Down
2 changes: 1 addition & 1 deletion package/harvester-os/files/etc/cos/bootargs.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set crash_kernel_params="crashkernel=219M,high crashkernel=72M,low"
if [ "${img}" == "/cOS/recovery.img" ]; then
set kernelcmd="$console_params root=LABEL=$recovery_label cos-img/filename=$img rd.neednet=1 rd.cos.oemlabel=$oem_label rd.cos.mount=LABEL=$oem_label:/oem rd.cos.oemtimeout=120"
else
set kernelcmd="$console_params root=LABEL=$state_label cos-img/filename=$img panic=0 net.ifnames=1 rd.cos.oemlabel=$oem_label rd.cos.mount=LABEL=$oem_label:/oem rd.cos.mount=LABEL=$persistent_label:/usr/local rd.cos.oemtimeout=120 audit=1 audit_backlog_limit=8192 intel_iommu=on amd_iommu=on iommu=pt"
set kernelcmd="$console_params root=LABEL=$state_label cos-img/filename=$img panic=0 net.ifnames=1 rd.cos.oemlabel=$oem_label rd.cos.mount=LABEL=$oem_label:/oem rd.cos.mount=LABEL=$persistent_label:/usr/local rd.cos.oemtimeout=120 audit=1 audit_backlog_limit=8192 intel_iommu=on amd_iommu=on iommu=pt $third_party_kernel_args"
fi

set initramfs=/boot/initrd
8 changes: 8 additions & 0 deletions package/harvester-os/files/usr/sbin/harv-install
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,14 @@ update_grub_settings()
if ! [ -f ${TARGET_FILE} ]; then
touch ${TARGET_FILE}
fi
# /etc/cos/bootargs.cfg appends a new variable $third_party_kernel_args
# if harvester config has os.externalStorageConfig.additionalKernelArguments specified
# then these will be mapped to HARVESTER_ADDITIONAL_KERNEL_ARGUMENTS
# and will be added to /oem/grubenv file
TARGET_FILE="${oem_dir}/grubenv"
if [ -n "${HARVESTER_ADDITIONAL_KERNEL_ARGUMENTS}" ]; then
grub2-editenv ${TARGET_FILE} set third_party_kernel_args="${HARVESTER_ADDITIONAL_KERNEL_ARGUMENTS}"
fi

add_debug_grub_entry
}
Expand Down
14 changes: 13 additions & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,19 @@ type OS struct {
Labels map[string]string `json:"labels,omitempty"`
SSHD SSHDConfig `json:"sshd,omitempty"`

PersistentStatePaths []string `json:"persistentStatePaths,omitempty"`
PersistentStatePaths []string `json:"persistentStatePaths,omitempty"`
ExternalStorage ExternalStorageConfig `json:"externalStorageConfig,omitempty"`
AdditionalKernelArguments string `json:"additionalKernelArguments,omitempty"`
}

type ExternalStorageConfig struct {
Enabled bool `json:"enabled,omitempty"`
MultiPathConfig []DiskConfig `json:"multiPathConfig,omitempty"`
}

type DiskConfig struct {
Vendor string `json:"vendor"`
Product string `json:"product"`
}

// SSHDConfig is the SSHD configuration for the node
Expand Down
46 changes: 46 additions & 0 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -539,3 +540,48 @@ func TestCalculateCPUReservedInMilliCPU(t *testing.T) {
assert.Equal(t, tc.reservedMilliCores, calculateCPUReservedInMilliCPU(tc.coreNum, tc.maxPods))
}
}
func Test_MultipathConfig(t *testing.T) {
assert := require.New(t)
config := NewHarvesterConfig()
config.OS.ExternalStorage = ExternalStorageConfig{
Enabled: true,
MultiPathConfig: []DiskConfig{
{
Vendor: "DELL",
Product: "DISK1",
},
{
Vendor: "HPE",
Product: "DISK2",
},
},
}

content, err := render("multipath.conf.tmpl", config)
assert.NoError(err, "expected no error while rending multipath config")
t.Log("rendered multipath config:")
t.Log(content)
}

func Test_ToCosInstallEnv(t *testing.T) {
hvConfig := NewHarvesterConfig()
hvConfig.OS.ExternalStorage = ExternalStorageConfig{
Enabled: true,
MultiPathConfig: []DiskConfig{
{
Vendor: "DELL",
Product: "DISK1",
},
{
Vendor: "HPE",
Product: "DISK2",
},
},
}
hvConfig.OS.AdditionalKernelArguments = "rd.iscsi.firmware rd.iscsi.ibft"
assert := require.New(t)
env, err := hvConfig.ToCosInstallEnv()
assert.NoError(err)
t.Log(env)

}
26 changes: 26 additions & 0 deletions pkg/config/cos.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@ func ConvertToCOS(config *HarvesterConfig) (*yipSchema.YipConfig, error) {
})
}

// enable multipathd for external storage support
if err := setupExternalStorage(config, &initramfs); err != nil {
return nil, err
}

// TOP
if cfg.Mode != ModeInstall {
if err := initRancherdStage(config, &initramfs); err != nil {
Expand Down Expand Up @@ -840,3 +845,24 @@ func CreateRootPartitioningLayout(elementalConfig *ElementalConfig, hvstConfig *

return elementalConfig, nil
}

// setupExternalStorage is needed to support boot of external disks
// this involves enable multipath service and configuring it to blacklist
// all devices except the ones listed in the config.OS.ExternalStorage.MultiPathConfig

func setupExternalStorage(config *HarvesterConfig, stage *yipSchema.Stage) error {
if !config.OS.ExternalStorage.Enabled {
return nil
}
stage.Systemctl.Enable = append(stage.Systemctl.Enable, "multipathd")
content, err := render("multipath.conf.tmpl", config)
if err != nil {
return fmt.Errorf("error rending multipath.conf template: %v", err)
}
stage.Files = append(stage.Files, yipSchema.File{
Path: "/etc/multipath.conf",
Content: content,
Permissions: 0755,
})
return nil
}
8 changes: 8 additions & 0 deletions pkg/config/templates/multipath.conf.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
blacklist {
{{ range $val := .ExternalStorage.MultiPathConfig }}
device {
vendor "!{{ $val.Vendor }}"
product "!{{ $val.Product }}"
}
{{ end }}
}
6 changes: 4 additions & 2 deletions pkg/console/install_panels.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,11 +289,13 @@ func getDataDiskOptions(hvstConfig *config.HarvesterConfig) ([]widgets.Option, e
}

func getDiskOptions() ([]widgets.Option, error) {
output, err := exec.Command("/bin/sh", "-c", `lsblk -r -o NAME,SIZE,TYPE | grep -w disk | cut -d ' ' -f 1,2`).CombinedOutput()
output, err := exec.Command("/bin/sh", "-c", `lsblk -J -o NAME,SIZE,TYPE,WWN,SERIAL`).CombinedOutput()
if err != nil {
return nil, err
}
lines := strings.Split(strings.TrimSuffix(string(output), "\n"), "\n")

lines, err := identifyUniqueDisks(output)

var options []widgets.Option
for _, line := range lines {
splits := strings.SplitN(line, " ", 2)
Expand Down
49 changes: 48 additions & 1 deletion pkg/console/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"net"
"os"
"os/exec"
"syscall"
Expand All @@ -14,6 +15,8 @@ import (
"golang.org/x/sys/unix"
"gopkg.in/yaml.v3"

"github.com/dell/goiscsi"

"github.com/harvester/harvester-installer/pkg/config"
)

Expand Down Expand Up @@ -137,7 +140,7 @@ func getNICs() ([]netlink.Link, error) {
}
}

return nics, nil
return filterISCSIInterfaces(nics)
}

func getNICState(name string) int {
Expand Down Expand Up @@ -190,3 +193,47 @@ func getManagementInterfaceName(mgmtInterface config.Network) string {
}
return mgmtName
}

// filterISCSIInterfaces will query the host to identify iscsi sessions, and skip interfaces
// used by the existing iscsi session.
func filterISCSIInterfaces(links []netlink.Link) ([]netlink.Link, error) {
iscsi := goiscsi.NewLinuxISCSI(nil)
sessions, err := iscsi.GetSessions()
if err != nil {
return nil, fmt.Errorf("error querying iscsi sessions: %v", err)
}

var returnLinks []netlink.Link
for _, link := range links {
var inuse bool
if getNICState(link.Attrs().Name) == NICStateUP {
iface, err := net.InterfaceByName(link.Attrs().Name)
if err != nil {
return nil, fmt.Errorf("error fetching interface details: %v", err)
}

addresses, err := iface.Addrs()
if err != nil {
return nil, fmt.Errorf("error fetching addresses from interface: %v", err)
}

for _, address := range addresses {
// interface addresses are in cidr format, and need to be converted before comparison
// since iscsi session contains just the ip address
ipAddress, _, err := net.ParseCIDR(address.String())
if err != nil {
return nil, fmt.Errorf("error parsing ip address: %v", err)
}
for _, session := range sessions {
if session.IfaceIPaddress == ipAddress.String() {
inuse = true
}
}
}
}
if !inuse {
returnLinks = append(returnLinks, link)
}
}
return returnLinks, nil
}
79 changes: 79 additions & 0 deletions pkg/console/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,10 @@ func doInstall(g *gocui.Gui, hvstConfig *config.HarvesterConfig, webhooks Render
env = append(env, fmt.Sprintf("HARVESTER_DATA_DISK=%s", hvstConfig.DataDisk))
}

if hvstConfig.OS.AdditionalKernelArguments != "" {
env = append(env, fmt.Sprintf("HARVESTER_ADDITIONAL_KERNEL_ARGUMENTS=%s", hvstConfig.OS.AdditionalKernelArguments))
}

elementalConfigDir, elementalConfigFile, err := saveElementalConfig(elementalConfig)
if err != nil {
return nil
Expand Down Expand Up @@ -982,3 +986,78 @@ func generateEnvAndConfig(g *gocui.Gui, hvstConfig *config.HarvesterConfig) ([]s
env = append(env, fmt.Sprintf("HARVESTER_STREAMDISK_CLOUDINIT_URL=%s", userDataURL))
return env, elementalConfig, nil
}

// internal objects to parse lsblk output
type BlockDevices struct {
Disks []Device `json:"blockdevices"`
}

type Device struct {
Name string `json:"name"`
Size string `json:"size"`
DiskType string `json:"type"`
WWN string `json:"wwn,omitempty"`
Serial string `json:"serial,omitempty"`
Children []Device `json:"children,omitempty"`
}

func generateDiskEntry(d Device) string {
return fmt.Sprintf("%s %s", d.Name, d.Size)
}

const (
diskType = "disk"
)

// identifyUniqueDisks parses the json output of lsblk and identifies
// unique disks by comparing their serial number info and wwn details
func identifyUniqueDisks(output []byte) ([]string, error) {
var returnDisks []string
disks := &BlockDevices{}
err := json.Unmarshal(output, disks)
if err != nil {
return nil, fmt.Errorf("error unmarshalling lsblk json output: %v", err)
}

// identify devices which may be unique
dedupMap := make(map[string]Device)
for _, disk := range disks.Disks {
if disk.DiskType == diskType {
// no serial or wwn info present
// add to list of disks
if disk.WWN == "" && disk.Serial == "" {
returnDisks = append(returnDisks, generateDiskEntry(disk))
continue
}

if disk.Serial != "" {
_, ok := dedupMap[disk.Serial]
if !ok {
dedupMap[disk.Serial] = disk
}
continue
}

if disk.WWN != "" {
_, ok := dedupMap[disk.WWN]
if !ok {
dedupMap[disk.WWN] = disk
}
continue
}
}
}
// devices may appear twice in the map when both serial number and wwn info is present
// we need to ensure only unique names are shown in the console
resultMap := make(map[string]Device)
for _, v := range dedupMap {
resultMap[v.Name] = v
}

// generate list of disks
for _, v := range resultMap {
returnDisks = append(returnDisks, generateDiskEntry(v))
}

return returnDisks, nil
}
Loading

0 comments on commit c240c65

Please sign in to comment.