Use uname machine field to get CPU variant if fails at /proc/cpuinfo
When images/containers in ARM arch were built/executed on x86 host, getCPUVariant will fail as it tries to look for /proc/cpuinfo, whose content is from the host. Adding a new method as fallback to check uname machine when it happens. Signed-off-by: Tony Fang <nenghui.fang@gmail.com>
This commit is contained in:
parent
8f7cfbda29
commit
8d5c045435
@ -17,14 +17,9 @@
|
|||||||
package platforms
|
package platforms
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/containerd/containerd/errdefs"
|
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -37,108 +32,12 @@ var cpuVariantOnce sync.Once
|
|||||||
func cpuVariant() string {
|
func cpuVariant() string {
|
||||||
cpuVariantOnce.Do(func() {
|
cpuVariantOnce.Do(func() {
|
||||||
if isArmArch(runtime.GOARCH) {
|
if isArmArch(runtime.GOARCH) {
|
||||||
cpuVariantValue = getCPUVariant()
|
var err error
|
||||||
|
cpuVariantValue, err = getCPUVariant()
|
||||||
|
if err != nil {
|
||||||
|
log.L.Errorf("Error getCPUVariant for OS %s : %v", runtime.GOOS, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return cpuVariantValue
|
return cpuVariantValue
|
||||||
}
|
}
|
||||||
|
|
||||||
// For Linux, the kernel has already detected the ABI, ISA and Features.
|
|
||||||
// So we don't need to access the ARM registers to detect platform information
|
|
||||||
// by ourselves. We can just parse these information from /proc/cpuinfo
|
|
||||||
func getCPUInfo(pattern string) (info string, err error) {
|
|
||||||
if !isLinuxOS(runtime.GOOS) {
|
|
||||||
return "", fmt.Errorf("getCPUInfo for OS %s: %w", runtime.GOOS, errdefs.ErrNotImplemented)
|
|
||||||
}
|
|
||||||
|
|
||||||
cpuinfo, err := os.Open("/proc/cpuinfo")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer cpuinfo.Close()
|
|
||||||
|
|
||||||
// Start to Parse the Cpuinfo line by line. For SMP SoC, we parse
|
|
||||||
// the first core is enough.
|
|
||||||
scanner := bufio.NewScanner(cpuinfo)
|
|
||||||
for scanner.Scan() {
|
|
||||||
newline := scanner.Text()
|
|
||||||
list := strings.Split(newline, ":")
|
|
||||||
|
|
||||||
if len(list) > 1 && strings.EqualFold(strings.TrimSpace(list[0]), pattern) {
|
|
||||||
return strings.TrimSpace(list[1]), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether the scanner encountered errors
|
|
||||||
err = scanner.Err()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("getCPUInfo for pattern: %s: %w", pattern, errdefs.ErrNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCPUVariant() string {
|
|
||||||
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
|
|
||||||
// Windows/Darwin only supports v7 for ARM32 and v8 for ARM64 and so we can use
|
|
||||||
// runtime.GOARCH to determine the variants
|
|
||||||
var variant string
|
|
||||||
switch runtime.GOARCH {
|
|
||||||
case "arm64":
|
|
||||||
variant = "v8"
|
|
||||||
case "arm":
|
|
||||||
variant = "v7"
|
|
||||||
default:
|
|
||||||
variant = "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
return variant
|
|
||||||
}
|
|
||||||
if runtime.GOOS == "freebsd" {
|
|
||||||
// FreeBSD supports ARMv6 and ARMv7 as well as ARMv4 and ARMv5 (though deprecated)
|
|
||||||
// detecting those variants is currently unimplemented
|
|
||||||
var variant string
|
|
||||||
switch runtime.GOARCH {
|
|
||||||
case "arm64":
|
|
||||||
variant = "v8"
|
|
||||||
default:
|
|
||||||
variant = "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
return variant
|
|
||||||
}
|
|
||||||
|
|
||||||
variant, err := getCPUInfo("Cpu architecture")
|
|
||||||
if err != nil {
|
|
||||||
log.L.WithError(err).Error("failure getting variant")
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle edge case for Raspberry Pi ARMv6 devices (which due to a kernel quirk, report "CPU architecture: 7")
|
|
||||||
// https://www.raspberrypi.org/forums/viewtopic.php?t=12614
|
|
||||||
if runtime.GOARCH == "arm" && variant == "7" {
|
|
||||||
model, err := getCPUInfo("model name")
|
|
||||||
if err == nil && strings.HasPrefix(strings.ToLower(model), "armv6-compatible") {
|
|
||||||
variant = "6"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch strings.ToLower(variant) {
|
|
||||||
case "8", "aarch64":
|
|
||||||
variant = "v8"
|
|
||||||
case "7", "7m", "?(12)", "?(13)", "?(14)", "?(15)", "?(16)", "?(17)":
|
|
||||||
variant = "v7"
|
|
||||||
case "6", "6tej":
|
|
||||||
variant = "v6"
|
|
||||||
case "5", "5t", "5te", "5tej":
|
|
||||||
variant = "v5"
|
|
||||||
case "4", "4t":
|
|
||||||
variant = "v4"
|
|
||||||
case "3":
|
|
||||||
variant = "v3"
|
|
||||||
default:
|
|
||||||
variant = "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
return variant
|
|
||||||
}
|
|
||||||
|
161
platforms/cpuinfo_linux.go
Normal file
161
platforms/cpuinfo_linux.go
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
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 platforms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getMachineArch retrieves the machine architecture through system call
|
||||||
|
func getMachineArch() (string, error) {
|
||||||
|
var uname unix.Utsname
|
||||||
|
err := unix.Uname(&uname)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
arch := string(uname.Machine[:bytes.IndexByte(uname.Machine[:], 0)])
|
||||||
|
|
||||||
|
return arch, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Linux, the kernel has already detected the ABI, ISA and Features.
|
||||||
|
// So we don't need to access the ARM registers to detect platform information
|
||||||
|
// by ourselves. We can just parse these information from /proc/cpuinfo
|
||||||
|
func getCPUInfo(pattern string) (info string, err error) {
|
||||||
|
|
||||||
|
cpuinfo, err := os.Open("/proc/cpuinfo")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer cpuinfo.Close()
|
||||||
|
|
||||||
|
// Start to Parse the Cpuinfo line by line. For SMP SoC, we parse
|
||||||
|
// the first core is enough.
|
||||||
|
scanner := bufio.NewScanner(cpuinfo)
|
||||||
|
for scanner.Scan() {
|
||||||
|
newline := scanner.Text()
|
||||||
|
list := strings.Split(newline, ":")
|
||||||
|
|
||||||
|
if len(list) > 1 && strings.EqualFold(strings.TrimSpace(list[0]), pattern) {
|
||||||
|
return strings.TrimSpace(list[1]), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the scanner encountered errors
|
||||||
|
err = scanner.Err()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("getCPUInfo for pattern: %s: %w", pattern, errdefs.ErrNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCPUVariantFromArch get CPU variant from arch through a system call
|
||||||
|
func getCPUVariantFromArch(arch string) (string, error) {
|
||||||
|
|
||||||
|
var variant string
|
||||||
|
|
||||||
|
arch = strings.ToLower(arch)
|
||||||
|
|
||||||
|
if arch == "aarch64" {
|
||||||
|
variant = "8"
|
||||||
|
} else if len(arch) >= 5 {
|
||||||
|
//Valid arch format is in form of armvXx
|
||||||
|
switch arch[3:5] {
|
||||||
|
case "v8":
|
||||||
|
variant = "8"
|
||||||
|
case "v7":
|
||||||
|
variant = "7"
|
||||||
|
case "v6":
|
||||||
|
variant = "6"
|
||||||
|
case "v5":
|
||||||
|
variant = "5"
|
||||||
|
case "v4":
|
||||||
|
variant = "4"
|
||||||
|
case "v3":
|
||||||
|
variant = "3"
|
||||||
|
default:
|
||||||
|
variant = "unknown"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("getCPUVariantFromArch invalid arch : %s, %v", arch, errdefs.ErrInvalidArgument)
|
||||||
|
}
|
||||||
|
return variant, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCPUVariant returns cpu variant for ARM
|
||||||
|
// We first try reading "Cpu architecture" field from /proc/cpuinfo
|
||||||
|
// If we can't find it, then fall back using a system call
|
||||||
|
// This is to cover running ARM in emulated environment on x86 host as this field in /proc/cpuinfo
|
||||||
|
// was not present.
|
||||||
|
func getCPUVariant() (string, error) {
|
||||||
|
|
||||||
|
variant, err := getCPUInfo("Cpu architecture")
|
||||||
|
if err != nil {
|
||||||
|
if errdefs.IsNotFound(err) {
|
||||||
|
//Let's try getting CPU variant from machine architecture
|
||||||
|
arch, err := getMachineArch()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failure getting machine architecture : %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
variant, err = getCPUVariantFromArch(arch)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failure getting CPU variant from machine architecture : %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("failure getting CPU variant : %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle edge case for Raspberry Pi ARMv6 devices (which due to a kernel quirk, report "CPU architecture: 7")
|
||||||
|
// https://www.raspberrypi.org/forums/viewtopic.php?t=12614
|
||||||
|
if runtime.GOARCH == "arm" && variant == "7" {
|
||||||
|
model, err := getCPUInfo("model name")
|
||||||
|
if err == nil && strings.HasPrefix(strings.ToLower(model), "armv6-compatible") {
|
||||||
|
variant = "6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch strings.ToLower(variant) {
|
||||||
|
case "8", "aarch64":
|
||||||
|
variant = "v8"
|
||||||
|
case "7", "7m", "?(12)", "?(13)", "?(14)", "?(15)", "?(16)", "?(17)":
|
||||||
|
variant = "v7"
|
||||||
|
case "6", "6tej":
|
||||||
|
variant = "v6"
|
||||||
|
case "5", "5t", "5te", "5tej":
|
||||||
|
variant = "v5"
|
||||||
|
case "4", "4t":
|
||||||
|
variant = "v4"
|
||||||
|
case "3":
|
||||||
|
variant = "v3"
|
||||||
|
default:
|
||||||
|
variant = "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
return variant, nil
|
||||||
|
}
|
@ -22,13 +22,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestCPUVariant(t *testing.T) {
|
func TestCPUVariant(t *testing.T) {
|
||||||
if !isArmArch(runtime.GOARCH) || !isLinuxOS(runtime.GOOS) {
|
if !isArmArch(runtime.GOARCH) {
|
||||||
t.Skip("only relevant on linux/arm")
|
t.Skip("only relevant on linux/arm")
|
||||||
}
|
}
|
||||||
|
|
||||||
variants := []string{"v8", "v7", "v6", "v5", "v4", "v3"}
|
variants := []string{"v8", "v7", "v6", "v5", "v4", "v3"}
|
||||||
|
|
||||||
p := getCPUVariant()
|
p, err := getCPUVariant()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error getting CPU variant: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for _, variant := range variants {
|
for _, variant := range variants {
|
||||||
if p == variant {
|
if p == variant {
|
||||||
t.Logf("got valid variant as expected: %#v = %#v\n", p, variant)
|
t.Logf("got valid variant as expected: %#v = %#v\n", p, variant)
|
||||||
@ -38,3 +43,11 @@ func TestCPUVariant(t *testing.T) {
|
|||||||
|
|
||||||
t.Fatalf("could not get valid variant as expected: %v\n", variants)
|
t.Fatalf("could not get valid variant as expected: %v\n", variants)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetCPUVariantFromArch(t *testing.T) {
|
||||||
|
|
||||||
|
if !isArmArch(runtime.GOARCH) {
|
||||||
|
t.Skip("only relevant on linux/arm")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
57
platforms/cpuinfo_other.go
Normal file
57
platforms/cpuinfo_other.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
//go:build !linux
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
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 platforms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getCPUVariant() (string, error) {
|
||||||
|
|
||||||
|
var variant string
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
|
||||||
|
// Windows/Darwin only supports v7 for ARM32 and v8 for ARM64 and so we can use
|
||||||
|
// runtime.GOARCH to determine the variants
|
||||||
|
switch runtime.GOARCH {
|
||||||
|
case "arm64":
|
||||||
|
variant = "v8"
|
||||||
|
case "arm":
|
||||||
|
variant = "v7"
|
||||||
|
default:
|
||||||
|
variant = "unknown"
|
||||||
|
}
|
||||||
|
} else if runtime.GOOS == "freebsd" {
|
||||||
|
// FreeBSD supports ARMv6 and ARMv7 as well as ARMv4 and ARMv5 (though deprecated)
|
||||||
|
// detecting those variants is currently unimplemented
|
||||||
|
switch runtime.GOARCH {
|
||||||
|
case "arm64":
|
||||||
|
variant = "v8"
|
||||||
|
default:
|
||||||
|
variant = "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("getCPUVariant for OS %s: %v", runtime.GOOS, errdefs.ErrNotImplemented)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return variant, nil
|
||||||
|
}
|
@ -21,13 +21,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// isLinuxOS returns true if the operating system is Linux.
|
|
||||||
//
|
|
||||||
// The OS value should be normalized before calling this function.
|
|
||||||
func isLinuxOS(os string) bool {
|
|
||||||
return os == "linux"
|
|
||||||
}
|
|
||||||
|
|
||||||
// These function are generated from https://golang.org/src/go/build/syslist.go.
|
// These function are generated from https://golang.org/src/go/build/syslist.go.
|
||||||
//
|
//
|
||||||
// We use switch statements because they are slightly faster than map lookups
|
// We use switch statements because they are slightly faster than map lookups
|
||||||
|
Loading…
Reference in New Issue
Block a user