This repository has been archived by the owner on May 12, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 113
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #133 from sboeuf/fix_device_passing
grpc: Handle hotplugged devices
- Loading branch information
Showing
7 changed files
with
872 additions
and
164 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
// | ||
// Copyright (c) 2018 Intel Corporation | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
package main | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
"syscall" | ||
"time" | ||
|
||
"github.com/kata-containers/agent/pkg/uevent" | ||
pb "github.com/kata-containers/agent/protocols/grpc" | ||
"github.com/sirupsen/logrus" | ||
"google.golang.org/grpc/codes" | ||
grpcStatus "google.golang.org/grpc/status" | ||
) | ||
|
||
type deviceDriversHandler func(device pb.Device, spec *pb.Spec) error | ||
|
||
var deviceDriversHandlerList = map[string]deviceDriversHandler{ | ||
"blk": blockDeviceHandler, | ||
} | ||
|
||
func blockDeviceHandler(device pb.Device, spec *pb.Spec) error { | ||
// First need to make sure the expected device shows up properly, | ||
// and then we need to retrieve its device info (such as major and | ||
// minor numbers), useful to update the device provided | ||
// through the OCI specification. | ||
if err := waitForDevice(device.VmPath); err != nil { | ||
return err | ||
} | ||
|
||
// If no ContainerPath is provided, this means we don't expect the | ||
// device to be updated in the specification. We can return from here. | ||
if device.ContainerPath == "" { | ||
return nil | ||
} | ||
|
||
// At this point in the code, we assume the specification will be | ||
// updated, meaning we should make sure we have valid pointers here. | ||
if spec.Linux == nil || len(spec.Linux.Devices) == 0 { | ||
return grpcStatus.Errorf(codes.Internal, | ||
"No devices found from the spec, cannot update") | ||
} | ||
|
||
stat := syscall.Stat_t{} | ||
if err := syscall.Stat(device.VmPath, &stat); err != nil { | ||
return err | ||
} | ||
|
||
major := int64(stat.Rdev / 256) | ||
minor := int64(stat.Rdev % 256) | ||
|
||
agentLog.WithFields(logrus.Fields{ | ||
"device-path": device.VmPath, | ||
"device-major": major, | ||
"device-minor": minor, | ||
}).Info("handling block device") | ||
|
||
// Update the spec | ||
updated := false | ||
for idx, d := range spec.Linux.Devices { | ||
if d.Path == device.ContainerPath { | ||
agentLog.WithFields(logrus.Fields{ | ||
"device-path": device.VmPath, | ||
"host-device-major": spec.Linux.Devices[idx].Major, | ||
"host-device-minor": spec.Linux.Devices[idx].Minor, | ||
"guest-device-major": major, | ||
"guest-device-minor": minor, | ||
}).Info("updating block device major/minor into the spec") | ||
spec.Linux.Devices[idx].Major = major | ||
spec.Linux.Devices[idx].Minor = minor | ||
updated = true | ||
break | ||
} | ||
} | ||
|
||
if !updated { | ||
return grpcStatus.Errorf(codes.Internal, | ||
"Should have found a matching device %s in the spec", | ||
device.VmPath) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func waitForDevice(devicePath string) error { | ||
deviceName := strings.TrimPrefix(devicePath, devPrefix) | ||
|
||
if _, err := os.Stat(devicePath); err == nil { | ||
return nil | ||
} | ||
|
||
uEvHandler, err := uevent.NewHandler() | ||
if err != nil { | ||
return err | ||
} | ||
defer uEvHandler.Close() | ||
|
||
fieldLogger := agentLog.WithField("device", deviceName) | ||
|
||
// Check if the device already exists. | ||
if _, err := os.Stat(devicePath); err == nil { | ||
fieldLogger.Info("Device already hotplugged, quit listening") | ||
return nil | ||
} | ||
|
||
fieldLogger.Info("Started listening for uevents for device hotplug") | ||
|
||
// Channel to signal when desired uevent has been received. | ||
done := make(chan bool) | ||
|
||
go func() { | ||
// This loop will be either ended if the hotplugged device is | ||
// found by listening to the netlink socket, or it will end | ||
// after the function returns and the uevent handler is closed. | ||
for { | ||
uEv, err := uEvHandler.Read() | ||
if err != nil { | ||
fieldLogger.Error(err) | ||
continue | ||
} | ||
|
||
fieldLogger = fieldLogger.WithFields(logrus.Fields{ | ||
"uevent-action": uEv.Action, | ||
"uevent-devpath": uEv.DevPath, | ||
"uevent-subsystem": uEv.SubSystem, | ||
"uevent-seqnum": uEv.SeqNum, | ||
}) | ||
|
||
fieldLogger.Info("Got uevent") | ||
|
||
if uEv.Action == "add" && | ||
filepath.Base(uEv.DevPath) == deviceName { | ||
fieldLogger.Info("Hotplug event received") | ||
break | ||
} | ||
} | ||
|
||
close(done) | ||
}() | ||
|
||
select { | ||
case <-done: | ||
case <-time.After(time.Duration(timeoutHotplug) * time.Second): | ||
return grpcStatus.Errorf(codes.DeadlineExceeded, | ||
"Timeout reached after %ds waiting for device %s", | ||
timeoutHotplug, deviceName) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// | ||
// Copyright (c) 2018 Intel Corporation | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
package main | ||
|
||
import ( | ||
"io/ioutil" | ||
"os" | ||
"testing" | ||
|
||
pb "github.com/kata-containers/agent/protocols/grpc" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func createFakeDevicePath() (string, error) { | ||
f, err := ioutil.TempFile("", "fake-dev-path") | ||
if err != nil { | ||
return "", err | ||
} | ||
path := f.Name() | ||
f.Close() | ||
|
||
return path, nil | ||
} | ||
|
||
func testblockDeviceHandlerSuccessful(t *testing.T, device pb.Device, spec *pb.Spec) { | ||
devPath, err := createFakeDevicePath() | ||
assert.Nil(t, err, "Fake device path creation failed: %v", err) | ||
defer os.RemoveAll(devPath) | ||
|
||
device.VmPath = devPath | ||
|
||
err = blockDeviceHandler(device, spec) | ||
assert.Nil(t, err, "blockDeviceHandler() failed: %v", err) | ||
} | ||
|
||
func TestBlockDeviceHandlerNilLinuxSpecSuccessful(t *testing.T) { | ||
spec := &pb.Spec{} | ||
|
||
testblockDeviceHandlerSuccessful(t, pb.Device{}, spec) | ||
} | ||
|
||
func testblockDeviceHandlerFailure(t *testing.T, device pb.Device, spec *pb.Spec) { | ||
devPath, err := createFakeDevicePath() | ||
assert.Nil(t, err, "Fake device path creation failed: %v", err) | ||
defer os.RemoveAll(devPath) | ||
|
||
device.VmPath = devPath | ||
device.ContainerPath = "some-not-empty-path" | ||
|
||
err = blockDeviceHandler(device, spec) | ||
assert.NotNil(t, err, "blockDeviceHandler() should have failed") | ||
} | ||
|
||
func TestBlockDeviceHandlerNilLinuxSpecFailure(t *testing.T) { | ||
spec := &pb.Spec{} | ||
|
||
testblockDeviceHandlerFailure(t, pb.Device{}, spec) | ||
} | ||
|
||
func TestBlockDeviceHandlerEmptyLinuxDevicesSpecFailure(t *testing.T) { | ||
spec := &pb.Spec{ | ||
Linux: &pb.Linux{}, | ||
} | ||
|
||
testblockDeviceHandlerFailure(t, pb.Device{}, spec) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.