mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
Merge pull request #8530 from swagiaal/add-fomat-and-mount
Support formatting and mounting GCE PD without 'safe_format_and_mount'
This commit is contained in:
commit
a5fe33b8a3
@ -18,7 +18,12 @@ limitations under the License.
|
||||
// an alternate platform, we will need to abstract further.
|
||||
package mount
|
||||
|
||||
import "github.com/golang/glog"
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kubernetes/pkg/util/exec"
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
// Mount mounts source to target as fstype with given options.
|
||||
@ -44,6 +49,69 @@ type MountPoint struct {
|
||||
Pass int
|
||||
}
|
||||
|
||||
// SafeFormatAndMount probes a device to see if it is formatted. If
|
||||
// so it mounts it otherwise it formats it and mounts it
|
||||
type SafeFormatAndMount struct {
|
||||
Interface
|
||||
Runner exec.Interface
|
||||
}
|
||||
|
||||
// Mount mounts the given disk. If the disk is not formatted and the disk is not being mounted as read only
|
||||
// it will format the disk first then mount it.
|
||||
func (mounter *SafeFormatAndMount) Mount(source string, target string, fstype string, options []string) error {
|
||||
// Don't attempt to format if mounting as readonly. Go straight to mounting.
|
||||
for _, option := range options {
|
||||
if option == "ro" {
|
||||
return mounter.Interface.Mount(source, target, fstype, options)
|
||||
}
|
||||
}
|
||||
return mounter.formatAndMount(source, target, fstype, options)
|
||||
}
|
||||
|
||||
// formatAndMount uses unix utils to format and mount the given disk
|
||||
func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error {
|
||||
options = append(options, "defaults")
|
||||
|
||||
// Try to mount the disk
|
||||
err := mounter.Interface.Mount(source, target, fstype, options)
|
||||
if err != nil {
|
||||
// It is possible that this disk is not formatted. Double check using 'file'
|
||||
notFormatted, err := mounter.diskLooksUnformatted(source)
|
||||
if err == nil && notFormatted {
|
||||
// Disk is unformatted so format it.
|
||||
// Use 'ext4' as the default
|
||||
if len(fstype) == 0 {
|
||||
fstype = "ext4"
|
||||
}
|
||||
args := []string{"-E", "lazy_itable_init=0,lazy_journal_init=0", "-F", source}
|
||||
cmd := mounter.Runner.Command("mkfs."+fstype, args...)
|
||||
_, err := cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
// the disk has been formatted sucessfully try to mount it again.
|
||||
return mounter.Interface.Mount(source, target, fstype, options)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// diskLooksUnformatted uses 'file' to see if the given disk is unformated
|
||||
func (mounter *SafeFormatAndMount) diskLooksUnformatted(disk string) (bool, error) {
|
||||
args := []string{"-L", "--special-files", disk}
|
||||
cmd := mounter.Runner.Command("file", args...)
|
||||
dataOut, err := cmd.CombinedOutput()
|
||||
|
||||
// TODO (#13212): check if this disk has partitions and return false, and
|
||||
// an error if so.
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return !strings.Contains(string(dataOut), "filesystem"), nil
|
||||
}
|
||||
|
||||
// New returns a mount.Interface for the current system.
|
||||
func New() Interface {
|
||||
return &Mounter{}
|
||||
|
172
pkg/util/mount/safe_format_and_mount_test.go
Normal file
172
pkg/util/mount/safe_format_and_mount_test.go
Normal file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package mount
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/util/exec"
|
||||
)
|
||||
|
||||
type ErrorMounter struct {
|
||||
*FakeMounter
|
||||
errIndex int
|
||||
err []error
|
||||
}
|
||||
|
||||
func (mounter *ErrorMounter) Mount(source string, target string, fstype string, options []string) error {
|
||||
i := mounter.errIndex
|
||||
mounter.errIndex++
|
||||
if mounter.err != nil && mounter.err[i] != nil {
|
||||
return mounter.err[i]
|
||||
}
|
||||
return mounter.FakeMounter.Mount(source, target, fstype, options)
|
||||
}
|
||||
|
||||
type ExecArgs struct {
|
||||
command string
|
||||
args []string
|
||||
output string
|
||||
err error
|
||||
}
|
||||
|
||||
func TestSafeFormatAndMount(t *testing.T) {
|
||||
tests := []struct {
|
||||
fstype string
|
||||
mountOptions []string
|
||||
execScripts []ExecArgs
|
||||
mountErrs []error
|
||||
expectedError error
|
||||
}{
|
||||
{ // Test a read only mount
|
||||
fstype: "ext4",
|
||||
mountOptions: []string{"ro"},
|
||||
},
|
||||
{ // Test a normal mount
|
||||
fstype: "ext4",
|
||||
},
|
||||
|
||||
{ // Test that 'file' is called and fails
|
||||
fstype: "ext4",
|
||||
mountErrs: []error{fmt.Errorf("unknown filesystem type '(null)'")},
|
||||
execScripts: []ExecArgs{
|
||||
{"file", []string{"-L", "--special-files", "/dev/foo"}, "ext4 filesystem", nil},
|
||||
},
|
||||
expectedError: fmt.Errorf("unknown filesystem type '(null)'"),
|
||||
},
|
||||
{ // Test that 'file' is called and confirms unformatted disk, format fails
|
||||
fstype: "ext4",
|
||||
mountErrs: []error{fmt.Errorf("unknown filesystem type '(null)'")},
|
||||
execScripts: []ExecArgs{
|
||||
{"file", []string{"-L", "--special-files", "/dev/foo"}, "data", nil},
|
||||
{"mkfs.ext4", []string{"-E", "lazy_itable_init=0,lazy_journal_init=0", "-F", "/dev/foo"}, "", fmt.Errorf("formatting failed")},
|
||||
},
|
||||
expectedError: fmt.Errorf("formatting failed"),
|
||||
},
|
||||
{ // Test that 'file' is called and confirms unformatted disk, format passes, second mount fails
|
||||
fstype: "ext4",
|
||||
mountErrs: []error{fmt.Errorf("unknown filesystem type '(null)'"), fmt.Errorf("Still cannot mount")},
|
||||
execScripts: []ExecArgs{
|
||||
{"file", []string{"-L", "--special-files", "/dev/foo"}, "data", nil},
|
||||
{"mkfs.ext4", []string{"-E", "lazy_itable_init=0,lazy_journal_init=0", "-F", "/dev/foo"}, "", nil},
|
||||
},
|
||||
expectedError: fmt.Errorf("Still cannot mount"),
|
||||
},
|
||||
{ // Test that 'file' is called and confirms unformatted disk, format passes, second mount passes
|
||||
fstype: "ext4",
|
||||
mountErrs: []error{fmt.Errorf("unknown filesystem type '(null)'"), nil},
|
||||
execScripts: []ExecArgs{
|
||||
{"file", []string{"-L", "--special-files", "/dev/foo"}, "data", nil},
|
||||
{"mkfs.ext4", []string{"-E", "lazy_itable_init=0,lazy_journal_init=0", "-F", "/dev/foo"}, "", nil},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{ // Test that 'file' is called and confirms unformatted disk, format passes, second mount passes with ext3
|
||||
fstype: "ext3",
|
||||
mountErrs: []error{fmt.Errorf("unknown filesystem type '(null)'"), nil},
|
||||
execScripts: []ExecArgs{
|
||||
{"file", []string{"-L", "--special-files", "/dev/foo"}, "data", nil},
|
||||
{"mkfs.ext3", []string{"-E", "lazy_itable_init=0,lazy_journal_init=0", "-F", "/dev/foo"}, "", nil},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
commandScripts := []exec.FakeCommandAction{}
|
||||
for _, expected := range test.execScripts {
|
||||
ecmd := expected.command
|
||||
eargs := expected.args
|
||||
output := expected.output
|
||||
err := expected.err
|
||||
commandScript := func(cmd string, args ...string) exec.Cmd {
|
||||
if cmd != ecmd {
|
||||
t.Errorf("Unexpected command %s. Expecting %s", cmd, ecmd)
|
||||
}
|
||||
|
||||
for j := range args {
|
||||
if args[j] != eargs[j] {
|
||||
t.Errorf("Unexpected args %v. Expecting %v", args, eargs)
|
||||
}
|
||||
}
|
||||
fake := exec.FakeCmd{
|
||||
CombinedOutputScript: []exec.FakeCombinedOutputAction{
|
||||
func() ([]byte, error) { return []byte(output), err },
|
||||
},
|
||||
}
|
||||
return exec.InitFakeCmd(&fake, cmd, args...)
|
||||
}
|
||||
commandScripts = append(commandScripts, commandScript)
|
||||
}
|
||||
|
||||
fake := exec.FakeExec{
|
||||
CommandScript: commandScripts,
|
||||
}
|
||||
|
||||
fakeMounter := ErrorMounter{&FakeMounter{}, 0, test.mountErrs}
|
||||
mounter := SafeFormatAndMount{
|
||||
Interface: &fakeMounter,
|
||||
Runner: &fake,
|
||||
}
|
||||
|
||||
device := "/dev/foo"
|
||||
dest := "/mnt/bar"
|
||||
err := mounter.Mount(device, dest, test.fstype, test.mountOptions)
|
||||
if test.expectedError == nil {
|
||||
if err != nil {
|
||||
t.Errorf("unexpected non-error: %v", err)
|
||||
}
|
||||
|
||||
// Check that something was mounted on the directory
|
||||
isNotMountPoint, err := fakeMounter.IsLikelyNotMountPoint(dest)
|
||||
if err != nil || isNotMountPoint {
|
||||
t.Errorf("the directory was not mounted")
|
||||
}
|
||||
|
||||
//check that the correct device was mounted
|
||||
mountedDevice, _, err := GetDeviceNameFromMount(fakeMounter.FakeMounter, dest)
|
||||
if err != nil || mountedDevice != device {
|
||||
t.Errorf("the correct device was not mounted")
|
||||
}
|
||||
} else {
|
||||
if err == nil || test.expectedError.Error() != err.Error() {
|
||||
t.Errorf("unexpected error: %v. Expecting %v", err, test.expectedError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -106,7 +106,7 @@ func (plugin *awsElasticBlockStorePlugin) newBuilderInternal(spec *volume.Spec,
|
||||
fsType: fsType,
|
||||
partition: partition,
|
||||
readOnly: readOnly,
|
||||
diskMounter: &awsSafeFormatAndMount{mounter, exec.New()}}, nil
|
||||
diskMounter: &mount.SafeFormatAndMount{mounter, exec.New()}}, nil
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) NewCleaner(volName string, podUID types.UID, mounter mount.Interface) (volume.Cleaner, error) {
|
||||
|
@ -18,13 +18,10 @@ package aws_ebs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kubernetes/pkg/util/exec"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
)
|
||||
|
||||
type AWSDiskUtil struct{}
|
||||
@ -110,37 +107,3 @@ func (util *AWSDiskUtil) DetachDisk(c *awsElasticBlockStoreCleaner) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// safe_format_and_mount is a utility script on AWS VMs that probes a persistent disk, and if
|
||||
// necessary formats it before mounting it.
|
||||
// This eliminates the necessity to format a PD before it is used with a Pod on AWS.
|
||||
// TODO: port this script into Go and use it for all Linux platforms
|
||||
type awsSafeFormatAndMount struct {
|
||||
mount.Interface
|
||||
runner exec.Interface
|
||||
}
|
||||
|
||||
// uses /usr/share/google/safe_format_and_mount to optionally mount, and format a disk
|
||||
func (mounter *awsSafeFormatAndMount) Mount(source string, target string, fstype string, options []string) error {
|
||||
// Don't attempt to format if mounting as readonly. Go straight to mounting.
|
||||
// Don't attempt to format if mounting as readonly. Go straight to mounting.
|
||||
for _, option := range options {
|
||||
if option == "ro" {
|
||||
return mounter.Interface.Mount(source, target, fstype, options)
|
||||
}
|
||||
}
|
||||
args := []string{}
|
||||
// ext4 is the default for safe_format_and_mount
|
||||
if len(fstype) > 0 && fstype != "ext4" {
|
||||
args = append(args, "-m", fmt.Sprintf("mkfs.%s", fstype))
|
||||
}
|
||||
args = append(args, options...)
|
||||
args = append(args, source, target)
|
||||
glog.V(5).Infof("exec-ing: /usr/share/google/safe_format_and_mount %v", args)
|
||||
cmd := mounter.runner.Command("/usr/share/google/safe_format_and_mount", args...)
|
||||
dataOut, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
glog.V(5).Infof("error running /usr/share/google/safe_format_and_mount\n%s", string(dataOut))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -1,84 +0,0 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package aws_ebs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/util/exec"
|
||||
)
|
||||
|
||||
func TestSafeFormatAndMount(t *testing.T) {
|
||||
tests := []struct {
|
||||
fstype string
|
||||
expectedArgs []string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
fstype: "ext4",
|
||||
expectedArgs: []string{"/dev/foo", "/mnt/bar"},
|
||||
},
|
||||
{
|
||||
fstype: "vfat",
|
||||
expectedArgs: []string{"-m", "mkfs.vfat", "/dev/foo", "/mnt/bar"},
|
||||
},
|
||||
{
|
||||
err: fmt.Errorf("test error"),
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
|
||||
var cmdOut string
|
||||
var argsOut []string
|
||||
fake := exec.FakeExec{
|
||||
CommandScript: []exec.FakeCommandAction{
|
||||
func(cmd string, args ...string) exec.Cmd {
|
||||
cmdOut = cmd
|
||||
argsOut = args
|
||||
fake := exec.FakeCmd{
|
||||
CombinedOutputScript: []exec.FakeCombinedOutputAction{
|
||||
func() ([]byte, error) { return []byte{}, test.err },
|
||||
},
|
||||
}
|
||||
return exec.InitFakeCmd(&fake, cmd, args...)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mounter := awsSafeFormatAndMount{
|
||||
runner: &fake,
|
||||
}
|
||||
|
||||
err := mounter.Mount("/dev/foo", "/mnt/bar", test.fstype, nil)
|
||||
if test.err == nil && err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if test.err != nil {
|
||||
if err == nil {
|
||||
t.Errorf("unexpected non-error")
|
||||
}
|
||||
return
|
||||
}
|
||||
if cmdOut != "/usr/share/google/safe_format_and_mount" {
|
||||
t.Errorf("unexpected command: %s", cmdOut)
|
||||
}
|
||||
if len(argsOut) != len(test.expectedArgs) {
|
||||
t.Errorf("unexpected args: %v, expected: %v", argsOut, test.expectedArgs)
|
||||
}
|
||||
}
|
||||
}
|
@ -103,7 +103,7 @@ func (plugin *gcePersistentDiskPlugin) newBuilderInternal(spec *volume.Spec, pod
|
||||
},
|
||||
fsType: fsType,
|
||||
readOnly: readOnly,
|
||||
diskMounter: &gceSafeFormatAndMount{mounter, exec.New()}}, nil
|
||||
diskMounter: &mount.SafeFormatAndMount{mounter, exec.New()}}, nil
|
||||
}
|
||||
|
||||
func (plugin *gcePersistentDiskPlugin) NewCleaner(volName string, podUID types.UID, mounter mount.Interface) (volume.Cleaner, error) {
|
||||
|
@ -29,7 +29,6 @@ import (
|
||||
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
|
||||
"k8s.io/kubernetes/pkg/util"
|
||||
"k8s.io/kubernetes/pkg/util/exec"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
"k8s.io/kubernetes/pkg/util/operationmanager"
|
||||
)
|
||||
|
||||
@ -332,36 +331,3 @@ func udevadmChangeToDrive(drivePath string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// safe_format_and_mount is a utility script on GCE VMs that probes a persistent disk, and if
|
||||
// necessary formats it before mounting it.
|
||||
// This eliminates the necesisty to format a PD before it is used with a Pod on GCE.
|
||||
// TODO: port this script into Go and use it for all Linux platforms
|
||||
type gceSafeFormatAndMount struct {
|
||||
mount.Interface
|
||||
runner exec.Interface
|
||||
}
|
||||
|
||||
// uses /usr/share/google/safe_format_and_mount to optionally mount, and format a disk
|
||||
func (mounter *gceSafeFormatAndMount) Mount(source string, target string, fstype string, options []string) error {
|
||||
// Don't attempt to format if mounting as readonly. Go straight to mounting.
|
||||
for _, option := range options {
|
||||
if option == "ro" {
|
||||
return mounter.Interface.Mount(source, target, fstype, options)
|
||||
}
|
||||
}
|
||||
args := []string{}
|
||||
// ext4 is the default for safe_format_and_mount
|
||||
if len(fstype) > 0 && fstype != "ext4" {
|
||||
args = append(args, "-m", fmt.Sprintf("mkfs.%s", fstype))
|
||||
}
|
||||
args = append(args, options...)
|
||||
args = append(args, source, target)
|
||||
glog.V(5).Infof("exec-ing: /usr/share/google/safe_format_and_mount %v", args)
|
||||
cmd := mounter.runner.Command("/usr/share/google/safe_format_and_mount", args...)
|
||||
dataOut, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
glog.Errorf("error running /usr/share/google/safe_format_and_mount\n%s", string(dataOut))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -1,84 +0,0 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package gce_pd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/util/exec"
|
||||
)
|
||||
|
||||
func TestSafeFormatAndMount(t *testing.T) {
|
||||
tests := []struct {
|
||||
fstype string
|
||||
expectedArgs []string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
fstype: "ext4",
|
||||
expectedArgs: []string{"/dev/foo", "/mnt/bar"},
|
||||
},
|
||||
{
|
||||
fstype: "vfat",
|
||||
expectedArgs: []string{"-m", "mkfs.vfat", "/dev/foo", "/mnt/bar"},
|
||||
},
|
||||
{
|
||||
err: fmt.Errorf("test error"),
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
|
||||
var cmdOut string
|
||||
var argsOut []string
|
||||
fake := exec.FakeExec{
|
||||
CommandScript: []exec.FakeCommandAction{
|
||||
func(cmd string, args ...string) exec.Cmd {
|
||||
cmdOut = cmd
|
||||
argsOut = args
|
||||
fake := exec.FakeCmd{
|
||||
CombinedOutputScript: []exec.FakeCombinedOutputAction{
|
||||
func() ([]byte, error) { return []byte{}, test.err },
|
||||
},
|
||||
}
|
||||
return exec.InitFakeCmd(&fake, cmd, args...)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mounter := gceSafeFormatAndMount{
|
||||
runner: &fake,
|
||||
}
|
||||
|
||||
err := mounter.Mount("/dev/foo", "/mnt/bar", test.fstype, nil)
|
||||
if test.err == nil && err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if test.err != nil {
|
||||
if err == nil {
|
||||
t.Errorf("unexpected non-error")
|
||||
}
|
||||
return
|
||||
}
|
||||
if cmdOut != "/usr/share/google/safe_format_and_mount" {
|
||||
t.Errorf("unexpected command: %s", cmdOut)
|
||||
}
|
||||
if len(argsOut) != len(test.expectedArgs) {
|
||||
t.Errorf("unexpected args: %v, expected: %v", argsOut, test.expectedArgs)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user