github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/tools/virsh.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  	"errors"
    21  	"fmt"
    22  	"io"
    23  	"os"
    24  
    25  	"github.com/renstrom/dedent"
    26  	"github.com/spf13/cobra"
    27  )
    28  
    29  // virshCommand contains the data needed by the virsh subcommand
    30  // which allows one to execute virsh commands for a VM pod.
    31  type virshCommand struct {
    32  	client         KubeClient
    33  	nodeName       string
    34  	args           []string
    35  	out            io.Writer
    36  	realArgs       []string
    37  	domainNodeName string
    38  	virtletPodName string
    39  }
    40  
    41  // NewVirshCmd returns a cobra.Command that executes virsh for a VM pod.
    42  func NewVirshCmd(client KubeClient, out io.Writer) *cobra.Command {
    43  	virsh := &virshCommand{client: client, out: out}
    44  	cmd := &cobra.Command{
    45  		Use:   "virsh [flags] virsh_command -- [virsh_command_args...]",
    46  		Short: "Execute a virsh command",
    47  		Long: dedent.Dedent(`
    48                          This command executes libvirt virsh command.
    49  
    50                          A VM pod name in the form @podname is translated to the
    51                          corresponding libvirt domain name. If @podname is specified,
    52                          the target k8s node name is inferred automatically based
    53                          on the information of the VM pod. In case if no @podname
    54                          is specified, the command is executed on every node
    55                          and the output for every node is prepended with a line
    56                          with the node name and corresponding Virtlet pod name.`),
    57  		RunE: func(cmd *cobra.Command, args []string) error {
    58  			virsh.args = args
    59  			return virsh.Run()
    60  		},
    61  	}
    62  	cmd.Flags().StringVar(&virsh.nodeName, "node", "", "the name of the target node")
    63  	return cmd
    64  }
    65  
    66  func (v *virshCommand) processArgs() error {
    67  	if len(v.args) == 0 {
    68  		return errors.New("missing virsh argument(s)")
    69  	}
    70  
    71  	v.realArgs = nil
    72  	for _, arg := range v.args {
    73  		if len(arg) < 2 || arg[0] != '@' {
    74  			v.realArgs = append(v.realArgs, arg)
    75  			continue
    76  		}
    77  		podName := arg[1:]
    78  		vmPodInfo, err := v.client.GetVMPodInfo(podName)
    79  		switch {
    80  		case err != nil:
    81  			return fmt.Errorf("can't get VM pod info for %q: %v", podName, err)
    82  		case v.domainNodeName == "":
    83  			v.domainNodeName = vmPodInfo.NodeName
    84  			v.virtletPodName = vmPodInfo.VirtletPodName
    85  		case v.domainNodeName != vmPodInfo.NodeName:
    86  			return errors.New("can't reference VM pods that run on different nodes")
    87  		}
    88  		v.realArgs = append(v.realArgs, vmPodInfo.LibvirtDomainName())
    89  	}
    90  
    91  	return nil
    92  }
    93  
    94  func (v *virshCommand) runInVirtletPod(virtletPodName string) error {
    95  	exitCode, err := v.client.ExecInContainer(virtletPodName, "libvirt", "kube-system", nil, v.out, os.Stderr, append([]string{"virsh"}, v.realArgs...))
    96  	if err != nil {
    97  		return fmt.Errorf("error executing virsh in Virtlet pod %q: %v", virtletPodName, err)
    98  	}
    99  	if exitCode != 0 {
   100  		return fmt.Errorf("virsh returned non-zero exit code %d", exitCode)
   101  	}
   102  	return nil
   103  }
   104  
   105  func (v *virshCommand) runOnAllNodes() error {
   106  	podNames, nodeNames, err := v.client.GetVirtletPodAndNodeNames()
   107  	if err != nil {
   108  		return err
   109  	}
   110  	gotErrors := false
   111  	for n, nodeName := range nodeNames {
   112  		fmt.Fprintf(v.out, "*** node: %s pod: %s ***\n", nodeName, podNames[n])
   113  		if err := v.runInVirtletPod(podNames[n]); err != nil {
   114  			fmt.Fprintf(v.out, "ERROR: %v\n", err)
   115  			gotErrors = true
   116  		}
   117  		fmt.Fprint(v.out, "\n")
   118  	}
   119  	if gotErrors {
   120  		return errors.New("some of the nodes returned errors")
   121  	}
   122  	return nil
   123  }
   124  
   125  // Run executes the command.
   126  func (v *virshCommand) Run() error {
   127  	if err := v.processArgs(); err != nil {
   128  		return err
   129  	}
   130  	switch {
   131  	case v.nodeName == "" && v.domainNodeName == "":
   132  		return v.runOnAllNodes()
   133  	case v.domainNodeName == "":
   134  		var err error
   135  		v.virtletPodName, err = v.client.GetVirtletPodNameForNode(v.nodeName)
   136  		if err != nil {
   137  			return fmt.Errorf("couldn't get Virtlet pod name for node %q: %v", v.nodeName, err)
   138  		}
   139  	case v.nodeName != "" && v.domainNodeName != v.nodeName:
   140  		return errors.New("--node specifies a node other than one that runs the VM pod")
   141  	}
   142  
   143  	return v.runInVirtletPod(v.virtletPodName)
   144  }