...

Source file src/github.com/chaos-mesh/chaos-mesh/pkg/chaosdaemon/jvm_server.go

Documentation: github.com/chaos-mesh/chaos-mesh/pkg/chaosdaemon

     1  // Copyright 2021 Chaos Mesh Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  // http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  
    16  package chaosdaemon
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"os"
    22  	"strings"
    23  
    24  	"github.com/golang/protobuf/ptypes/empty"
    25  	"github.com/pkg/errors"
    26  
    27  	"github.com/chaos-mesh/chaos-mesh/pkg/bpm"
    28  	pb "github.com/chaos-mesh/chaos-mesh/pkg/chaosdaemon/pb"
    29  	"github.com/chaos-mesh/chaos-mesh/pkg/chaosdaemon/util"
    30  )
    31  
    32  const (
    33  	bmInstallCommand = "bminstall.sh -b -Dorg.jboss.byteman.transform.all -Dorg.jboss.byteman.verbose -Dorg.jboss.byteman.compileToBytecode -p %d %d"
    34  	bmSubmitCommand  = "bmsubmit.sh -p %d -%s %s"
    35  )
    36  
    37  func (s *DaemonServer) InstallJVMRules(ctx context.Context,
    38  	req *pb.InstallJVMRulesRequest) (*empty.Empty, error) {
    39  	log := s.getLoggerFromContext(ctx)
    40  	log.Info("InstallJVMRules", "request", req)
    41  	pid, err := s.crClient.GetPidFromContainerID(ctx, req.ContainerId)
    42  	if err != nil {
    43  		log.Error(err, "GetPidFromContainerID")
    44  		return nil, err
    45  	}
    46  
    47  	containerPids := []uint32{pid}
    48  	childPids, err := util.GetChildProcesses(pid, log)
    49  	if err != nil {
    50  		log.Error(err, "GetChildProcesses")
    51  	}
    52  	containerPids = append(containerPids, childPids...)
    53  	for _, containerPid := range containerPids {
    54  		name, err := util.ReadCommName(int(containerPid))
    55  		if err != nil {
    56  			log.Error(err, "ReadCommName")
    57  			continue
    58  		}
    59  		if name == "java\n" {
    60  			pid = containerPid
    61  			break
    62  		}
    63  	}
    64  
    65  	bytemanHome := os.Getenv("BYTEMAN_HOME")
    66  	if len(bytemanHome) == 0 {
    67  		return nil, errors.New("environment variable BYTEMAN_HOME not set")
    68  	}
    69  
    70  	// Copy byteman.jar, byteman-helper.jar and chaos-agent.jar into container's namespace.
    71  	if req.EnterNS {
    72  		processBuilder := bpm.DefaultProcessBuilder("sh", "-c", fmt.Sprintf("mkdir -p %s/lib/", bytemanHome)).SetContext(ctx).SetNS(pid, bpm.MountNS)
    73  		output, err := processBuilder.Build(ctx).CombinedOutput()
    74  		if err != nil {
    75  			return nil, err
    76  		}
    77  		if len(output) > 0 {
    78  			log.Info("mkdir", "output", string(output))
    79  		}
    80  
    81  		jars := []string{"byteman.jar", "byteman-helper.jar", "chaos-agent.jar"}
    82  
    83  		for _, jar := range jars {
    84  			source := fmt.Sprintf("%s/lib/%s", bytemanHome, jar)
    85  			dest := fmt.Sprintf("/usr/local/byteman/lib/%s", jar)
    86  
    87  			output, err = copyFileAcrossNS(ctx, source, dest, pid)
    88  			if err != nil {
    89  				return nil, err
    90  			}
    91  
    92  			log.Info("copy", "jar name", jar, "from source", source, "to destination", dest, "output", string(output))
    93  		}
    94  	}
    95  
    96  	bmInstallCmd := fmt.Sprintf(bmInstallCommand, req.Port, pid)
    97  	processBuilder := bpm.DefaultProcessBuilder("sh", "-c", bmInstallCmd).SetContext(ctx)
    98  	if req.EnterNS {
    99  		processBuilder = processBuilder.EnableLocalMnt()
   100  	}
   101  
   102  	cmd := processBuilder.Build(ctx)
   103  	output, err := cmd.CombinedOutput()
   104  	if err != nil {
   105  		// this error will occured when install agent more than once, and will ignore this error and continue to submit rule
   106  		errMsg1 := "Agent JAR loaded but agent failed to initialize"
   107  
   108  		// these two errors will occured when java version less or euqal to 1.8, and don't know why
   109  		// but it can install agent success even with this error, so just ignore it now.
   110  		// TODO: Investigate the cause of these two error
   111  		errMsg2 := "Provider sun.tools.attach.LinuxAttachProvider not found"
   112  		errMsg3 := "install java.io.IOException: Non-numeric value found"
   113  
   114  		// this error is caused by the different attach result codes in different java versions. In fact, the agent has attached success, just ignore it here.
   115  		// refer to https://stackoverflow.com/questions/54340438/virtualmachine-attach-throws-com-sun-tools-attach-agentloadexception-0-when-usi/54454418#54454418
   116  		errMsg4 := "com.sun.tools.attach.AgentLoadException"
   117  		if !strings.Contains(string(output), errMsg1) && !strings.Contains(string(output), errMsg2) &&
   118  			!strings.Contains(string(output), errMsg3) && !strings.Contains(string(output), errMsg4) {
   119  			log.Error(err, string(output))
   120  			return nil, errors.Wrap(err, string(output))
   121  		}
   122  		log.Info("exec comamnd", "cmd", cmd.String(), "output", string(output), "error", err.Error())
   123  	}
   124  
   125  	// submit helper jar
   126  	bmSubmitCmd := fmt.Sprintf(bmSubmitCommand, req.Port, "b", fmt.Sprintf("%s/lib/byteman-helper.jar", os.Getenv("BYTEMAN_HOME")))
   127  	processBuilder = bpm.DefaultProcessBuilder("sh", "-c", bmSubmitCmd).SetContext(ctx)
   128  	if req.EnterNS {
   129  		processBuilder = processBuilder.SetNS(pid, bpm.NetNS)
   130  	}
   131  	output, err = processBuilder.Build(ctx).CombinedOutput()
   132  	if err != nil {
   133  		log.Error(err, string(output))
   134  		return nil, err
   135  	}
   136  	if len(output) > 0 {
   137  		log.Info("submit helper jar", "output", string(output))
   138  	}
   139  
   140  	// submit rules
   141  	filename, err := writeDataIntoFile(req.Rule, "rule.btm")
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  
   146  	bmSubmitCmd = fmt.Sprintf(bmSubmitCommand, req.Port, "l", filename)
   147  	processBuilder = bpm.DefaultProcessBuilder("sh", "-c", bmSubmitCmd).SetContext(ctx)
   148  	if req.EnterNS {
   149  		processBuilder = processBuilder.SetNS(pid, bpm.NetNS)
   150  	}
   151  	output, err = processBuilder.Build(ctx).CombinedOutput()
   152  	if err != nil {
   153  		log.Error(err, string(output))
   154  		return nil, errors.Wrap(err, string(output))
   155  	}
   156  	if len(output) > 0 {
   157  		log.Info("submit rules", "output", string(output))
   158  	}
   159  
   160  	return &empty.Empty{}, nil
   161  }
   162  
   163  func (s *DaemonServer) UninstallJVMRules(ctx context.Context,
   164  	req *pb.UninstallJVMRulesRequest) (*empty.Empty, error) {
   165  	log := s.getLoggerFromContext(ctx)
   166  	log.Info("InstallJVMRules", "request", req)
   167  	pid, err := s.crClient.GetPidFromContainerID(ctx, req.ContainerId)
   168  	if err != nil {
   169  		log.Error(err, "GetPidFromContainerID")
   170  		return nil, err
   171  	}
   172  
   173  	filename, err := writeDataIntoFile(req.Rule, "rule.btm")
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  	log.Info("create btm file", "file", filename)
   178  
   179  	bmSubmitCmd := fmt.Sprintf(bmSubmitCommand, req.Port, "u", filename)
   180  	processBuilder := bpm.DefaultProcessBuilder("sh", "-c", bmSubmitCmd).SetContext(ctx)
   181  	if req.EnterNS {
   182  		processBuilder = processBuilder.SetNS(pid, bpm.NetNS)
   183  	}
   184  	output, err := processBuilder.Build(ctx).CombinedOutput()
   185  	if err != nil {
   186  		log.Error(err, string(output))
   187  		if strings.Contains(string(output), "No rule scripts to remove") {
   188  			return &empty.Empty{}, nil
   189  		}
   190  		return nil, errors.Wrap(err, string(output))
   191  	}
   192  
   193  	if len(output) > 0 {
   194  		log.Info(string(output))
   195  	}
   196  
   197  	return &empty.Empty{}, nil
   198  }
   199  
   200  func writeDataIntoFile(data string, filename string) (string, error) {
   201  	tmpfile, err := os.CreateTemp("", filename)
   202  	if err != nil {
   203  		return "", err
   204  	}
   205  
   206  	if _, err := tmpfile.WriteString(data); err != nil {
   207  		return "", err
   208  	}
   209  
   210  	if err := tmpfile.Close(); err != nil {
   211  		return "", err
   212  	}
   213  
   214  	return tmpfile.Name(), err
   215  }
   216  
   217  func copyFileAcrossNS(ctx context.Context, source string, dest string, pid uint32) ([]byte, error) {
   218  	sourceFile, err := os.Open(source)
   219  	if err != nil {
   220  		return nil, err
   221  	}
   222  	defer sourceFile.Close()
   223  
   224  	processBuilder := bpm.DefaultProcessBuilder("sh", "-c", fmt.Sprintf("cat > %s", dest)).SetContext(ctx)
   225  	processBuilder = processBuilder.SetNS(pid, bpm.MountNS).SetStdin(sourceFile)
   226  	output, err := processBuilder.Build(ctx).CombinedOutput()
   227  	if err != nil {
   228  		return nil, err
   229  	}
   230  
   231  	return output, nil
   232  }
   233