|
| 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