github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/tools/diag.go (about) 1 /* 2 Copyright 2018 Mirantis 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package tools 18 19 import ( 20 "bytes" 21 "errors" 22 "fmt" 23 "io" 24 "io/ioutil" 25 "os" 26 "strings" 27 28 "github.com/spf13/cobra" 29 v1 "k8s.io/api/core/v1" 30 31 "github.com/Mirantis/virtlet/pkg/diag" 32 "github.com/Mirantis/virtlet/pkg/version" 33 ) 34 35 const ( 36 maxVirtletPodLogLines = 20000 37 sonobuoyPluginsConfigMapName = "sonobuoy-plugins-cm" 38 virtletSonobuoyPluginFileName = "virtlet.yaml" 39 sonobuoyPluginYaml = `sonobuoy-config: 40 driver: Job 41 plugin-name: virtlet 42 result-type: virtlet 43 spec: 44 command: 45 - /bin/bash 46 - -c 47 - /sonobuoy.sh && sleep 3600 48 env: 49 - name: RESULTS_DIR 50 value: /tmp/results 51 image: mirantis/virtlet$TAG 52 name: sonobuoy-virtlet 53 volumeMounts: 54 - mountPath: /tmp/results 55 name: results 56 readOnly: false 57 ` 58 ) 59 60 type diagDumpCommand struct { 61 client KubeClient 62 out io.Writer 63 outDir string 64 useJSON bool 65 } 66 67 // NewDiagDumpCommand returns a new cobra.Command that dumps 68 // diagnostic information 69 func NewDiagDumpCommand(client KubeClient, out io.Writer) *cobra.Command { 70 d := &diagDumpCommand{ 71 client: client, 72 out: out, 73 } 74 cmd := &cobra.Command{ 75 Use: "dump output_dir", 76 Short: "Dump Virtlet diagnostics information", 77 Long: "Pull Virtlet diagnostics information from the nodes and dump it as a directory tree or JSON", 78 RunE: func(cmd *cobra.Command, args []string) error { 79 switch { 80 case !d.useJSON && len(args) != 1: 81 return errors.New("Must specify output directory or --json") 82 case !d.useJSON: 83 d.outDir = args[0] 84 case len(args) != 0: 85 return errors.New("This command does not accept arguments") 86 } 87 return d.Run() 88 }, 89 } 90 cmd.Flags().BoolVar(&d.useJSON, "json", false, "Use JSON output") 91 return cmd 92 } 93 94 func (d *diagDumpCommand) diagResult() (diag.Result, error) { 95 dr := diag.Result{ 96 IsDir: true, 97 Name: "nodes", 98 Children: make(map[string]diag.Result), 99 } 100 podNames, nodeNames, err := d.client.GetVirtletPodAndNodeNames() 101 if err != nil { 102 return diag.Result{}, err 103 } 104 for n, podName := range podNames { 105 nodeName := nodeNames[n] 106 var buf bytes.Buffer 107 exitCode, err := d.client.ExecInContainer( 108 podName, "virtlet", "kube-system", nil, 109 &buf, os.Stderr, []string{"virtlet", "--diag"}) 110 var cur diag.Result 111 switch { 112 case err != nil: 113 cur = diag.Result{ 114 Ext: "err", 115 Error: fmt.Sprintf("node %q: error getting version from Virtlet pod %q: %v", nodeName, podName, err), 116 } 117 case exitCode != 0: 118 cur = diag.Result{ 119 Ext: "err", 120 Error: fmt.Sprintf("node %q: error getting version from Virtlet pod %q: exit code %d", nodeName, podName, exitCode), 121 } 122 default: 123 cur, err = diag.DecodeDiagnostics(buf.Bytes()) 124 if err != nil { 125 cur = diag.Result{ 126 Ext: "err", 127 Error: fmt.Sprintf("error unmarshalling the diagnostics: %v", err), 128 } 129 } 130 } 131 if cur.IsDir { 132 if sub, found := cur.Children["diagnostics"]; found && len(dr.Children) == 1 { 133 cur = sub 134 } 135 } 136 137 if cur.IsDir { 138 d.dumpLogs(&cur, podName, "virtlet") 139 d.dumpLogs(&cur, podName, "libvirt") 140 } 141 142 cur.Name = nodeName 143 dr.Children[nodeName] = cur 144 } 145 return dr, nil 146 } 147 148 func (d *diagDumpCommand) dumpLogs(dr *diag.Result, podName, containerName string) { 149 cur := diag.Result{ 150 Name: "virtlet-pod-" + containerName, 151 Ext: "log", 152 } 153 logs, err := d.client.PodLogs(podName, containerName, "kube-system", maxVirtletPodLogLines) 154 if err != nil { 155 cur.Error = err.Error() 156 } else { 157 cur.Data = string(logs) 158 } 159 dr.Children[cur.Name] = cur 160 } 161 162 func (d *diagDumpCommand) Run() error { 163 dr, err := d.diagResult() 164 if err != nil { 165 return err 166 } 167 if d.useJSON { 168 d.out.Write(dr.ToJSON()) 169 return nil 170 } 171 return dr.Unpack(d.outDir) 172 } 173 174 type diagUnpackCommand struct { 175 in io.Reader 176 outDir string 177 } 178 179 // NewDiagUnpackCommand returns a new cobra.Command that unpacks 180 // diagnostic information 181 func NewDiagUnpackCommand(in io.Reader) *cobra.Command { 182 d := &diagUnpackCommand{in: in} 183 return &cobra.Command{ 184 Use: "unpack output_dir", 185 Short: "Unpack Virtlet diagnostics information", 186 Long: "Read Virtlet diagnostics information as JSON from stdin and unpacks into a directory tree", 187 RunE: func(cmd *cobra.Command, args []string) error { 188 if len(args) != 1 { 189 return errors.New("Must specify output directory") 190 } 191 d.outDir = args[0] 192 return d.Run() 193 }, 194 } 195 } 196 197 func (d *diagUnpackCommand) Run() error { 198 data, err := ioutil.ReadAll(d.in) 199 if err != nil { 200 return err 201 } 202 dr, err := diag.DecodeDiagnostics(data) 203 if err != nil { 204 return err 205 } 206 return dr.Unpack(d.outDir) 207 } 208 209 type diagSonobuoyCommand struct { 210 in io.Reader 211 out io.Writer 212 tag string 213 } 214 215 // NewDiagSonobuoyCommand returns a new cobra.Command that adds 216 // Virtlet plugin to sonobuoy-generated yaml 217 func NewDiagSonobuoyCommand(in io.Reader, out io.Writer) *cobra.Command { 218 d := &diagSonobuoyCommand{in: in, out: out} 219 cmd := &cobra.Command{ 220 Use: "sonobuoy", 221 Short: "Add Virtlet sonobuoy plugin to the sonobuoy output", 222 Long: "Find and patch sonobuoy configmap in the yaml that's read from stdin to include Virtlet sonobuoy plugin", 223 RunE: func(cmd *cobra.Command, args []string) error { 224 if len(args) != 0 { 225 return errors.New("This command does not accept arguments") 226 } 227 return d.Run() 228 }, 229 } 230 cmd.Flags().StringVar(&d.tag, "tag", version.Get().ImageTag, "Set virtlet image tag for the plugin") 231 return cmd 232 } 233 234 func (d *diagSonobuoyCommand) getYaml() ([]byte, error) { 235 bs, err := ioutil.ReadAll(d.in) 236 if err != nil { 237 return nil, err 238 } 239 objs, err := LoadYaml(bs) 240 if err != nil { 241 return nil, err 242 } 243 if len(objs) == 0 { 244 return nil, errors.New("source yaml is empty") 245 } 246 found := false 247 for _, o := range objs { 248 cfgMap, ok := o.(*v1.ConfigMap) 249 if !ok { 250 continue 251 } 252 if cfgMap.Name != sonobuoyPluginsConfigMapName { 253 continue 254 } 255 found = true 256 tagStr := "" 257 if d.tag != "" { 258 tagStr = ":" + d.tag 259 } 260 yaml := strings.Replace(sonobuoyPluginYaml, "$TAG", tagStr, -1) 261 cfgMap.Data[virtletSonobuoyPluginFileName] = yaml 262 } 263 if !found { 264 return nil, fmt.Errorf("ConfigMap not found: %q", sonobuoyPluginsConfigMapName) 265 } 266 return ToYaml(objs) 267 } 268 269 func (d *diagSonobuoyCommand) Run() error { 270 bs, err := d.getYaml() 271 if err != nil { 272 return err 273 } 274 if _, err := d.out.Write(bs); err != nil { 275 return err 276 } 277 return nil 278 } 279 280 // NewDiagCommand returns a new cobra.Command that handles Virtlet 281 // diagnostics. 282 func NewDiagCommand(client KubeClient, in io.Reader, out io.Writer) *cobra.Command { 283 cmd := &cobra.Command{ 284 Use: "diag", 285 Short: "Virtlet diagnostics", 286 Long: "Retrieve and unpack Virtlet diagnostics information", 287 } 288 cmd.AddCommand(NewDiagDumpCommand(client, out)) 289 cmd.AddCommand(NewDiagUnpackCommand(in)) 290 cmd.AddCommand(NewDiagSonobuoyCommand(in, out)) 291 return cmd 292 }