Skip to content

Commit 2f5a44d

Browse files
committed
profilecreator: chore: refactor profilecreator.go
extract code which deals with mustgather or ghw in their own source files. Trivial code movement with no changes in behavior. Signed-off-by: Francesco Romani <fromani@redhat.com>
1 parent 9f6250e commit 2f5a44d

File tree

3 files changed

+322
-265
lines changed

3 files changed

+322
-265
lines changed
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*
14+
* Copyright 2021 Red Hat, Inc.
15+
*/
16+
17+
package profilecreator
18+
19+
import (
20+
"fmt"
21+
"os"
22+
"path"
23+
"sort"
24+
25+
"github.com/jaypipes/ghw"
26+
"github.com/jaypipes/ghw/pkg/cpu"
27+
"github.com/jaypipes/ghw/pkg/option"
28+
"github.com/jaypipes/ghw/pkg/topology"
29+
30+
v1 "k8s.io/api/core/v1"
31+
)
32+
33+
// NewGHWHandler is a handler to use ghw options corresponding to a node
34+
func NewGHWHandler(mustGatherDirPath string, node *v1.Node) (*GHWHandler, error) {
35+
nodeName := node.GetName()
36+
nodePathSuffix := path.Join(Nodes)
37+
nodepath, err := getMustGatherFullPathsWithFilter(mustGatherDirPath, nodePathSuffix, ClusterScopedResources)
38+
if err != nil {
39+
return nil, fmt.Errorf("can't obtain the node path %s: %v", nodeName, err)
40+
}
41+
_, err = os.Stat(path.Join(nodepath, nodeName, SysInfoFileName))
42+
if err != nil {
43+
return nil, fmt.Errorf("can't obtain the path: %s for node %s: %v", nodeName, nodepath, err)
44+
}
45+
options := ghw.WithSnapshot(ghw.SnapshotOptions{
46+
Path: path.Join(nodepath, nodeName, SysInfoFileName),
47+
})
48+
ghwHandler := &GHWHandler{snapShotOptions: options, Node: node}
49+
return ghwHandler, nil
50+
}
51+
52+
// GHWHandler is a wrapper around ghw to get the API object
53+
type GHWHandler struct {
54+
snapShotOptions *option.Option
55+
Node *v1.Node
56+
}
57+
58+
// CPU returns a CPUInfo struct that contains information about the CPUs on the host system
59+
func (ghwHandler GHWHandler) CPU() (*cpu.Info, error) {
60+
return ghw.CPU(ghwHandler.snapShotOptions)
61+
}
62+
63+
func (ghwHandler GHWHandler) SortedCPU() (*cpu.Info, error) {
64+
cpuInfo, err := ghw.CPU(ghwHandler.snapShotOptions)
65+
if err != nil {
66+
return nil, fmt.Errorf("can't obtain cpuInfo info from GHW snapshot: %v", err)
67+
}
68+
69+
sort.Slice(cpuInfo.Processors, func(x, y int) bool {
70+
return cpuInfo.Processors[x].ID < cpuInfo.Processors[y].ID
71+
})
72+
73+
for _, processor := range cpuInfo.Processors {
74+
for _, core := range processor.Cores {
75+
sort.Slice(core.LogicalProcessors, func(x, y int) bool {
76+
return core.LogicalProcessors[x] < core.LogicalProcessors[y]
77+
})
78+
}
79+
80+
sort.Slice(processor.Cores, func(x, y int) bool {
81+
return processor.Cores[x].ID < processor.Cores[y].ID
82+
})
83+
}
84+
85+
return cpuInfo, nil
86+
}
87+
88+
// SortedTopology returns a TopologyInfo struct that contains information about the Topology sorted by numa ids and cpu ids on the host system
89+
func (ghwHandler GHWHandler) SortedTopology() (*topology.Info, error) {
90+
topologyInfo, err := ghw.Topology(ghwHandler.snapShotOptions)
91+
if err != nil {
92+
return nil, fmt.Errorf("can't obtain topology info from GHW snapshot: %v", err)
93+
}
94+
sortTopology(topologyInfo)
95+
return topologyInfo, nil
96+
}
97+
98+
func sortTopology(topologyInfo *topology.Info) {
99+
sort.Slice(topologyInfo.Nodes, func(x, y int) bool {
100+
return topologyInfo.Nodes[x].ID < topologyInfo.Nodes[y].ID
101+
})
102+
for _, node := range topologyInfo.Nodes {
103+
for _, core := range node.Cores {
104+
sort.Slice(core.LogicalProcessors, func(x, y int) bool {
105+
return core.LogicalProcessors[x] < core.LogicalProcessors[y]
106+
})
107+
}
108+
sort.Slice(node.Cores, func(i, j int) bool {
109+
return node.Cores[i].LogicalProcessors[0] < node.Cores[j].LogicalProcessors[0]
110+
})
111+
}
112+
}
113+
114+
func (ghwHandler GHWHandler) GatherSystemInfo() (*systemInfo, error) {
115+
cpuInfo, err := ghwHandler.SortedCPU()
116+
if err != nil {
117+
return nil, err
118+
}
119+
120+
topologyInfo, err := ghwHandler.SortedTopology()
121+
if err != nil {
122+
return nil, err
123+
}
124+
125+
htEnabled, err := ghwHandler.IsHyperthreadingEnabled()
126+
if err != nil {
127+
return nil, err
128+
}
129+
130+
return &systemInfo{
131+
CpuInfo: &extendedCPUInfo{
132+
CpuInfo: cpuInfo,
133+
NumLogicalProcessorsUsed: make(map[int]int, len(cpuInfo.Processors)),
134+
LogicalProcessorsUsed: make(map[int]struct{}),
135+
},
136+
TopologyInfo: topologyInfo,
137+
HtEnabled: htEnabled,
138+
}, nil
139+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*
14+
* Copyright 2021 Red Hat, Inc.
15+
*/
16+
17+
package profilecreator
18+
19+
import (
20+
"fmt"
21+
"os"
22+
"path"
23+
"path/filepath"
24+
"strings"
25+
26+
machineconfigv1 "github.com/openshift/api/machineconfiguration/v1"
27+
v1 "k8s.io/api/core/v1"
28+
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
29+
)
30+
31+
const (
32+
// ClusterScopedResources defines the subpath, relative to the top-level must-gather directory.
33+
// A top-level must-gather directory is of the following format:
34+
// must-gather-dir/quay-io-openshift-kni-performance-addon-operator-must-gather-sha256-<Image SHA>
35+
// Here we find the cluster-scoped definitions saved by must-gather
36+
ClusterScopedResources = "cluster-scoped-resources"
37+
// CoreNodes defines the subpath, relative to ClusterScopedResources, on which we find node-specific data
38+
CoreNodes = "core/nodes"
39+
// MCPools defines the subpath, relative to ClusterScopedResources, on which we find the machine config pool definitions
40+
MCPools = "machineconfiguration.openshift.io/machineconfigpools"
41+
// YAMLSuffix is the extension of the yaml files saved by must-gather
42+
YAMLSuffix = ".yaml"
43+
// Nodes defines the subpath, relative to top-level must-gather directory, on which we find node-specific data
44+
Nodes = "nodes"
45+
// SysInfoFileName defines the name of the file where ghw snapshot is stored
46+
SysInfoFileName = "sysinfo.tgz"
47+
// defines the sub path relative to ClusterScopedResources, on which OCP infrastructure object is
48+
configOCPInfra = "config.openshift.io/infrastructures"
49+
)
50+
51+
func getMustGatherFullPathsWithFilter(mustGatherPath string, suffix string, filter string) (string, error) {
52+
var paths []string
53+
54+
// don't assume directory names, only look for the suffix, filter out files having "filter" in their names
55+
err := filepath.Walk(mustGatherPath, func(path string, info os.FileInfo, err error) error {
56+
if strings.HasSuffix(path, suffix) {
57+
if len(filter) == 0 || !strings.Contains(path, filter) {
58+
paths = append(paths, path)
59+
}
60+
}
61+
return nil
62+
})
63+
if err != nil {
64+
return "", fmt.Errorf("failed to get the path mustGatherPath:%s, suffix:%s %v", mustGatherPath, suffix, err)
65+
}
66+
if len(paths) == 0 {
67+
return "", fmt.Errorf("no match for the specified must gather directory path: %s and suffix: %s", mustGatherPath, suffix)
68+
}
69+
if len(paths) > 1 {
70+
Alert("Multiple matches for the specified must gather directory path: %s and suffix: %s", mustGatherPath, suffix)
71+
return "", fmt.Errorf("Multiple matches for the specified must gather directory path: %s and suffix: %s.\n Expected only one performance-addon-operator-must-gather* directory, please check the must-gather tarball", mustGatherPath, suffix)
72+
}
73+
// returning one possible path
74+
return paths[0], err
75+
}
76+
77+
func getMustGatherFullPaths(mustGatherPath string, suffix string) (string, error) {
78+
return getMustGatherFullPathsWithFilter(mustGatherPath, suffix, "")
79+
}
80+
81+
func getNode(mustGatherDirPath, nodeName string) (*v1.Node, error) {
82+
var node v1.Node
83+
nodePathSuffix := path.Join(ClusterScopedResources, CoreNodes, nodeName)
84+
path, err := getMustGatherFullPaths(mustGatherDirPath, nodePathSuffix)
85+
if err != nil {
86+
return nil, fmt.Errorf("failed to get MachineConfigPool for %s: %v", nodeName, err)
87+
}
88+
89+
src, err := os.Open(path)
90+
if err != nil {
91+
return nil, fmt.Errorf("failed to open %q: %v", path, err)
92+
}
93+
defer src.Close()
94+
95+
dec := k8syaml.NewYAMLOrJSONDecoder(src, 1024)
96+
if err := dec.Decode(&node); err != nil {
97+
return nil, fmt.Errorf("failed to decode %q: %v", path, err)
98+
}
99+
return &node, nil
100+
}
101+
102+
// GetNodeList returns the list of nodes using the Node YAMLs stored in Must Gather
103+
func GetNodeList(mustGatherDirPath string) ([]*v1.Node, error) {
104+
machines := make([]*v1.Node, 0)
105+
106+
nodePathSuffix := path.Join(ClusterScopedResources, CoreNodes)
107+
nodePath, err := getMustGatherFullPaths(mustGatherDirPath, nodePathSuffix)
108+
if err != nil {
109+
return nil, fmt.Errorf("failed to get Nodes from must gather directory: %v", err)
110+
}
111+
if nodePath == "" {
112+
return nil, fmt.Errorf("failed to get Nodes from must gather directory: %v", err)
113+
}
114+
115+
nodes, err := os.ReadDir(nodePath)
116+
if err != nil {
117+
return nil, fmt.Errorf("failed to list mustGatherPath directories: %v", err)
118+
}
119+
for _, node := range nodes {
120+
nodeName := node.Name()
121+
node, err := getNode(mustGatherDirPath, nodeName)
122+
if err != nil {
123+
return nil, fmt.Errorf("failed to get Nodes %s: %v", nodeName, err)
124+
}
125+
machines = append(machines, node)
126+
}
127+
return machines, nil
128+
}
129+
130+
// GetMCPList returns the list of MCPs using the mcp YAMLs stored in Must Gather
131+
func GetMCPList(mustGatherDirPath string) ([]*machineconfigv1.MachineConfigPool, error) {
132+
pools := make([]*machineconfigv1.MachineConfigPool, 0)
133+
134+
mcpPathSuffix := path.Join(ClusterScopedResources, MCPools)
135+
mcpPath, err := getMustGatherFullPaths(mustGatherDirPath, mcpPathSuffix)
136+
if err != nil {
137+
return nil, fmt.Errorf("failed to get MCPs: %v", err)
138+
}
139+
if mcpPath == "" {
140+
return nil, fmt.Errorf("failed to get MCPs path: %v", err)
141+
}
142+
143+
mcpFiles, err := os.ReadDir(mcpPath)
144+
if err != nil {
145+
return nil, fmt.Errorf("failed to list mustGatherPath directories: %v", err)
146+
}
147+
for _, mcp := range mcpFiles {
148+
mcpName := strings.TrimSuffix(mcp.Name(), filepath.Ext(mcp.Name()))
149+
150+
mcp, err := GetMCP(mustGatherDirPath, mcpName)
151+
// master pool relevant only when pods can be scheduled on masters, e.g. SNO
152+
if mcpName != "master" && err != nil {
153+
return nil, fmt.Errorf("can't obtain MCP %s: %v", mcpName, err)
154+
}
155+
pools = append(pools, mcp)
156+
}
157+
return pools, nil
158+
}
159+
160+
// GetMCP returns an MCP object corresponding to a specified MCP Name
161+
func GetMCP(mustGatherDirPath, mcpName string) (*machineconfigv1.MachineConfigPool, error) {
162+
var mcp machineconfigv1.MachineConfigPool
163+
164+
mcpPathSuffix := path.Join(ClusterScopedResources, MCPools, mcpName+YAMLSuffix)
165+
mcpPath, err := getMustGatherFullPaths(mustGatherDirPath, mcpPathSuffix)
166+
if err != nil {
167+
return nil, fmt.Errorf("failed to obtain MachineConfigPool %s: %v", mcpName, err)
168+
}
169+
if mcpPath == "" {
170+
return nil, fmt.Errorf("failed to obtain MachineConfigPool, mcp:%s does not exist: %v", mcpName, err)
171+
}
172+
173+
src, err := os.Open(mcpPath)
174+
if err != nil {
175+
return nil, fmt.Errorf("failed to open %q: %v", mcpPath, err)
176+
}
177+
defer src.Close()
178+
dec := k8syaml.NewYAMLOrJSONDecoder(src, 1024)
179+
if err := dec.Decode(&mcp); err != nil {
180+
return nil, fmt.Errorf("failed to decode %q: %v", mcpPath, err)
181+
}
182+
return &mcp, nil
183+
}

0 commit comments

Comments
 (0)