chaosd/pkg/core/network_command.go

278 lines
5.9 KiB
Go

// Copyright 2020 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
"github.com/pingcap/errors"
pb "github.com/chaos-mesh/chaos-daemon/pkg/server/serverpb"
"github.com/chaos-mesh/chaos-daemon/pkg/utils"
)
type NetworkCommand struct {
Action string
Latency string
Jitter string
Correlation string
Percent string
Device string
SourcePort string
EgressPort string
IPAddress string
IPProtocol string
Hostname string
}
const (
NetworkDelayAction = "delay"
NetworkLossAction = "loss"
)
func (n *NetworkCommand) Validate() error {
switch n.Action {
case NetworkDelayAction:
return n.validNetworkDelay()
case NetworkLossAction:
return n.validNetworkLoss()
default:
return errors.Errorf("network action %s not supported", n.Action)
}
}
func (n *NetworkCommand) validNetworkDelay() error {
if len(n.Latency) == 0 {
return errors.New("delay is required")
}
if _, err := time.ParseDuration(n.Latency); err != nil {
return errors.WithMessage(err, fmt.Sprintf("latency %s not valid", n.Latency))
}
if len(n.Jitter) > 0 {
if _, err := time.ParseDuration(n.Jitter); err != nil {
return errors.WithMessage(err, fmt.Sprintf("jitter %s not valid", n.Jitter))
}
}
if !utils.CheckPercent(n.Correlation) {
return errors.Errorf("correlation %s not valid", n.Correlation)
}
if len(n.Device) == 0 {
return errors.New("device is required")
}
if !utils.CheckIPs(n.IPAddress) {
return errors.Errorf("ip addressed %s not valid", n.IPAddress)
}
return checkProtocolAndPorts(n.IPProtocol, n.SourcePort, n.EgressPort)
}
func (n *NetworkCommand) validNetworkLoss() error {
if len(n.Percent) == 0 {
return errors.New("percent is required")
}
if !utils.CheckPercent(n.Percent) {
return errors.Errorf("percent %s not valid", n.Percent)
}
if !utils.CheckPercent(n.Correlation) {
return errors.Errorf("correlation %s not valid", n.Correlation)
}
if len(n.Device) == 0 {
return errors.New("device is required")
}
if !utils.CheckIPs(n.IPAddress) {
return errors.Errorf("ip addressed %s not valid", n.IPAddress)
}
return checkProtocolAndPorts(n.IPProtocol, n.SourcePort, n.EgressPort)
}
func (n *NetworkCommand) SetDefaultForNetworkDelay() {
if len(n.Jitter) == 0 {
n.Jitter = "0ms"
}
if len(n.Correlation) == 0 {
n.Correlation = "0"
}
}
func (n *NetworkCommand) SetDefaultForNetworkLoss() {
if len(n.Correlation) == 0 {
n.Correlation = "0"
}
}
func checkProtocolAndPorts(p string, sports string, dports string) error {
if !utils.CheckPorts(sports) {
return errors.Errorf("source ports %s not valid", sports)
}
if !utils.CheckPorts(dports) {
return errors.Errorf("egress ports %s not valid", dports)
}
if !utils.CheckIPProtocols(p) {
return errors.Errorf("ip protocols %s not valid", p)
}
if len(sports) > 0 || len(dports) > 0 {
if p == "tcp" || p == "udp" {
return nil
}
return errors.New("ip protocol is required")
}
return nil
}
func (n *NetworkCommand) String() string {
data, _ := json.Marshal(n)
return string(data)
}
func (n *NetworkCommand) ToDelayNetem() (*pb.Netem, error) {
delayTime, err := time.ParseDuration(n.Latency)
if err != nil {
return nil, errors.WithStack(err)
}
jitter, err := time.ParseDuration(n.Jitter)
if err != nil {
return nil, errors.WithStack(err)
}
corr, err := strconv.ParseFloat(n.Correlation, 32)
if err != nil {
return nil, errors.WithStack(err)
}
netem := &pb.Netem{
Device: n.Device,
Time: uint32(delayTime.Nanoseconds() / 1e3),
DelayCorr: float32(corr),
Jitter: uint32(jitter.Nanoseconds() / 1e3),
}
return netem, nil
}
func (n *NetworkCommand) ToLossNetem() (*pb.Netem, error) {
percent, err := strconv.ParseFloat(n.Percent, 32)
if err != nil {
return nil, errors.WithStack(err)
}
corr, err := strconv.ParseFloat(n.Correlation, 32)
if err != nil {
return nil, errors.WithStack(err)
}
return &pb.Netem{
Loss: float32(percent),
LossCorr: float32(corr),
}, nil
}
func (n *NetworkCommand) ToTC(ipset string) (*pb.Tc, error) {
tc := &pb.Tc{
Type: pb.Tc_NETEM,
Ipset: ipset,
Protocol: n.IPProtocol,
SourcePort: n.SourcePort,
EgressPort: n.EgressPort,
}
var (
netem *pb.Netem
err error
)
switch n.Action {
case NetworkDelayAction:
if netem, err = n.ToDelayNetem(); err != nil {
return nil, errors.WithStack(err)
}
case NetworkLossAction:
if netem, err = n.ToLossNetem(); err != nil {
return nil, errors.WithStack(err)
}
default:
return nil, errors.Errorf("action %s not supported", n.Action)
}
tc.Netem = netem
return tc, nil
}
func (n *NetworkCommand) ToIPSet(name string) (*pb.IPSet, error) {
var (
cidrs []string
err error
)
if len(n.IPAddress) > 0 {
cidrs, err = utils.ResolveCidrs(strings.Split(n.IPAddress, ","))
if err != nil {
return nil, errors.WithStack(err)
}
}
if len(n.Hostname) > 0 {
cs, err := utils.ResolveCidrs(strings.Split(n.Hostname, ","))
if err != nil {
return nil, errors.WithStack(err)
}
cidrs = append(cidrs, cs...)
}
return &pb.IPSet{
Name: name,
Cidrs: cidrs,
}, nil
}
func (n *NetworkCommand) NeedApplyIPSet() bool {
if len(n.IPAddress) > 0 || len(n.Hostname) > 0 {
return true
}
return false
}
func (n *NetworkCommand) NeedApplyIptables() bool {
return false
}
func (n *NetworkCommand) NeedApplyTC() bool {
return true
}
func (n *NetworkCommand) ToChain() (*pb.Chain, error) {
return nil, nil
}