Skip to content
This repository has been archived by the owner on Jul 22, 2024. It is now read-only.

Parse multipath result from multipathd -k #304

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions fakes/fake_executor.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 34 additions & 3 deletions utils/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,25 @@
package utils

import (
"bufio"
"bytes"
"context"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"time"
"strings"
"syscall"
"time"

"github.com/IBM/ubiquity/utils/logs"
)

//go:generate counterfeiter -o ../fakes/fake_executor.go . Executor
type Executor interface { // basic host dependent functions
Execute(command string, args []string) ([]byte, error)
ExecuteInteractive(command string, args []string, interactiveCmds []string) ([]byte, error)
Stat(string) (os.FileInfo, error)
Mkdir(string, os.FileMode) error
MkdirAll(string, os.FileMode) error
Expand All @@ -51,7 +54,6 @@ type Executor interface { // basic host dependent functions
IsSameFile(file1 os.FileInfo, file2 os.FileInfo) bool
IsDirEmpty(dir string) (bool, error)
GetDeviceForFileStat(os.FileInfo) uint64

}

type executor struct {
Expand Down Expand Up @@ -84,6 +86,35 @@ func (e *executor) Execute(command string, args []string) ([]byte, error) {
return stdOut, err
}

// non-interactive mode of interactive command(s) executor
// It will enter the interactive mode, run given command(s) and exit immediately.
// interactiveCmds is a set of commands run in interactive mode, an exit command is mandatory.
// For example: [command1, exit], [command1, command2, q]
func (e *executor) ExecuteInteractive(command string, args []string, interactiveCmds []string) ([]byte, error) {
var stdout, stderr bytes.Buffer
interactiveCmd := strings.Join(interactiveCmds, "\n") + "\n"
outWriter := bufio.NewWriter(&stdout)
errWriter := bufio.NewWriter(&stderr)
cmdStdin := strings.NewReader(interactiveCmd)

execInteractive := exec.Command(command, args...)
execInteractive.Stdout = outWriter
execInteractive.Stdin = cmdStdin
execInteractive.Stderr = errWriter

err := execInteractive.Run()
if err != nil {
return nil, err
}

errOutput := strings.TrimSpace(stderr.String())
if errOutput != "" {
return nil, fmt.Errorf(errOutput)
}

return stdout.Bytes(), nil
}

func (e *executor) ExecuteWithTimeout(mSeconds int, command string, args []string) ([]byte, error) {

// Create a new context and add a timeout to it
Expand Down Expand Up @@ -184,6 +215,6 @@ func (e *executor) IsDirEmpty(dir string) (bool, error) {
return len(files) == 0, nil
}

func (e *executor) GetDeviceForFileStat(fileStat os.FileInfo) uint64{
func (e *executor) GetDeviceForFileStat(fileStat os.FileInfo) uint64 {
return fileStat.Sys().(*syscall.Stat_t).Dev
}
66 changes: 61 additions & 5 deletions utils/mpath.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package utils

import (
"bufio"
"encoding/json"
"fmt"
"regexp"
"strings"
Expand Down Expand Up @@ -53,7 +54,7 @@ func GetMultipathOutputAndDeviceMapperAndDevice(volumeWwn string, exec Executor)
if err != nil {
return []byte{}, "", []string{}, err
}
bodyPattern := "[0-9]+:[0-9]+:[0-9]+:[0-9]+ "
bodyPattern := "(?:[0-9]+:[0-9]+:[0-9]+:[0-9]+ )[\\s\\S]+"
bodyRegex, err := regexp.Compile(bodyPattern)
if err != nil {
return []byte{}, "", []string{}, err
Expand All @@ -74,11 +75,9 @@ func GetMultipathOutputAndDeviceMapperAndDevice(volumeWwn string, exec Executor)
skipped := false
for scanner.Scan() {
text := scanner.Text()
text = strings.TrimSpace(text)
if bodyRegex.MatchString(text) {
index := bodyRegex.FindStringIndex(text)
trimedText := text[index[0]:]
deviceName := strings.Fields(trimedText)[1]
res := bodyRegex.FindString(text)
deviceName := strings.Fields(res)[1]
deviceNames = append(deviceNames, deviceName)
} else if !skipped {
skipped = true
Expand Down Expand Up @@ -112,3 +111,60 @@ func ExcludeNoTargetPortGroupMessagesFromMultipathOutput(mpathOutput string, log
regex, _ := regexp.Compile(WarningNoTargetPortGroup)
return excludeWarningMessageLines(mpathOutput, regex, logger)
}

// GetMultipathNameUuidpair returns all the multipath devices in the following format:
// ["mpatha,360050768029b8168e000000000006247", "mpathb,360050768029b8168e000000000006247", ...]
func GetMultipathNameUuidpair(exec Executor) ([]string, error) {
cmd := `show maps raw format "%n,%w"`
output, err := Multipathd(cmd, exec)
if err != nil {
return []string{}, err
} else {
pairs := strings.Split(output, "\n")
return pairs, nil
}
}

func GetMultipathOutputAll(exec Executor) (*MultipathOutputAll, error) {
cmd := "list maps json"
output, err := Multipathd(cmd, exec)
if err != nil {
return nil, err
} else {
var mpathAll MultipathOutputAll
err := json.Unmarshal([]byte(output), &mpathAll)
if err != nil {
return nil, err
}
return &mpathAll, nil
}
}

func GetMultipathOutput(name string, exec Executor) (*MultipathOutput, error) {
cmd := fmt.Sprintf("list map %s json", name)
output, err := Multipathd(cmd, exec)
if err != nil {
return nil, err
} else {
var mpath MultipathOutput
err := json.Unmarshal([]byte(output), &mpath)
if err != nil {
return nil, err
}
return &mpath, nil
}
}

// Multipathd is a non-interactive mode of "multipathd -k"
// It will enter the interactive mode, run given command and exit immediately.
func Multipathd(cmd string, exec Executor) (string, error) {

output, err := exec.ExecuteInteractive("multipathd", []string{"-k"}, []string{cmd, "exit"})
if err != nil {
return "", err
}
outputString := strings.TrimSpace(string(output))
outputString = strings.SplitN(outputString, "\n", 2)[1]
outputString = strings.TrimSpace(outputString)
return strings.Split(outputString, "\nmultipathd>")[0], nil
}
Loading