wrapper ipset util

pull/6/head
m1093782566 2017-11-05 19:23:29 +08:00
parent 338ee7f5d5
commit 45ad69765e
7 changed files with 1026 additions and 0 deletions

View File

@ -27,6 +27,7 @@ filegroup(
"//pkg/util/interrupt:all-srcs",
"//pkg/util/io:all-srcs",
"//pkg/util/ipconfig:all-srcs",
"//pkg/util/ipset:all-srcs",
"//pkg/util/iptables:all-srcs",
"//pkg/util/ipvs:all-srcs",
"//pkg/util/keymutex:all-srcs",

41
pkg/util/ipset/BUILD Normal file
View File

@ -0,0 +1,41 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"ipset.go",
"types.go",
],
importpath = "k8s.io/kubernetes/pkg/util/ipset",
visibility = ["//visibility:public"],
deps = ["//vendor/k8s.io/utils/exec:go_default_library"],
)
go_test(
name = "go_default_test",
srcs = ["ipset_test.go"],
importpath = "k8s.io/kubernetes/pkg/util/ipset",
library = ":go_default_library",
deps = [
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
"//vendor/k8s.io/utils/exec/testing:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/util/ipset/testing:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

325
pkg/util/ipset/ipset.go Normal file
View File

@ -0,0 +1,325 @@
/*
Copyright 2017 The Kubernetes 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,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ipset
import (
"bytes"
"fmt"
"regexp"
"strconv"
"strings"
utilexec "k8s.io/utils/exec"
)
// Interface is an injectable interface for running ipset commands. Implementations must be goroutine-safe.
type Interface interface {
// FlushSet deletes all entries from a named set.
FlushSet(set string) error
// DestroySet deletes a named set.
DestroySet(set string) error
// DestroyAllSets deletes all sets.
DestroyAllSets() error
// CreateSet creates a new set, it will ignore error when the set already exists if ignoreExistErr=true.
CreateSet(set *IPSet, ignoreExistErr bool) error
// AddEntry adds a new entry to the named set.
AddEntry(entry string, set string, ignoreExistErr bool) error
// DelEntry deletes one entry from the named set
DelEntry(entry string, set string) error
// Test test if an entry exists in the named set
TestEntry(entry string, set string) (bool, error)
// ListEntries lists all the entries from a named set
ListEntries(set string) ([]string, error)
// ListSets list all set names from kernel
ListSets() ([]string, error)
// GetVersion returns the "X.Y" version string for ipset.
GetVersion() (string, error)
}
// IPSetCmd represents the ipset util. We use ipset command for ipset execute.
const IPSetCmd = "ipset"
// EntryMemberPattern is the regular expression pattern of ipset member list.
// The raw output of ipset command `ipset list {set}` is similar to,
//Name: foobar
//Type: hash:ip,port
//Revision: 2
//Header: family inet hashsize 1024 maxelem 65536
//Size in memory: 16592
//References: 0
//Members:
//192.168.1.2,tcp:8080
//192.168.1.1,udp:53
var EntryMemberPattern = "(?m)^(.*\n)*Members:\n"
// VersionPattern is the regular expression pattern of ipset version string.
// ipset version output is similar to "v6.10".
var VersionPattern = "v[0-9]+\\.[0-9]+"
// IPSet implements an Interface to an set.
type IPSet struct {
// Name is the set name.
Name string
// SetType specifies the ipset type.
SetType Type
// HashFamily specifies the protocol family of the IP addresses to be stored in the set.
// The default is inet, i.e IPv4. If users want to use IPv6, they should specify inet6.
HashFamily string
// HashSize specifies the hash table size of ipset.
HashSize int
// MaxElem specifies the max element number of ipset.
MaxElem int
// PortRange specifies the port range of bitmap:port type ipset.
PortRange string
}
// Entry represents a ipset entry.
type Entry struct {
// IP is the entry's IP. The IP address protocol corresponds to the HashFamily of IPSet.
// All entries' IP addresses in the same ip set has same the protocol, IPv4 or IPv6.
IP string
// Port is the entry's Port.
Port int
// Protocol is the entry's Protocol. The protocols of entries in the same ip set are all
// the same. The accepted protocols are TCP and UDP.
Protocol string
// Net is the entry's IP network address. Network address with zero prefix size can NOT
// be stored.
Net string
// IP2 is the entry's second IP. IP2 may not be empty for `hash:ip,port,ip` type ip set.
IP2 string
// SetType specifies the type of ip set where the entry exists.
SetType Type
}
func (e *Entry) String() string {
switch e.SetType {
case HashIPPort:
// Entry{192.168.1.1, udp, 53} -> 192.168.1.1,udp:53
// Entry{192.168.1.2, tcp, 8080} -> 192.168.1.2,tcp:8080
return fmt.Sprintf("%s,%s:%s", e.IP, e.Protocol, strconv.Itoa(e.Port))
case HashIPPortIP:
// Entry{192.168.1.1, udp, 53, 10.0.0.1} -> 192.168.1.1,udp:53,10.0.0.1
// Entry{192.168.1.2, tcp, 8080, 192.168.1.2} -> 192.168.1.2,tcp:8080,192.168.1.2
return fmt.Sprintf("%s,%s:%s,%s", e.IP, e.Protocol, strconv.Itoa(e.Port), e.IP2)
case HashIPPortNet:
// Entry{192.168.1.2, udp, 80, 10.0.1.0/24} -> 192.168.1.2,udp:80,10.0.1.0/24
// Entry{192.168.2,25, tcp, 8080, 10.1.0.0/16} -> 192.168.2,25,tcp:8080,10.1.0.0/16
return fmt.Sprintf("%s,%s:%s,%s", e.IP, e.Protocol, strconv.Itoa(e.Port), e.Net)
case BitmapPort:
// Entry{53} -> 53
// Entry{8080} -> 8080
return strconv.Itoa(e.Port)
}
return ""
}
type runner struct {
exec utilexec.Interface
}
// New returns a new Interface which will exec ipset.
func New(exec utilexec.Interface) Interface {
return &runner{
exec: exec,
}
}
// CreateSet creates a new set, it will ignore error when the set already exists if ignoreExistErr=true.
func (runner *runner) CreateSet(set *IPSet, ignoreExistErr bool) error {
// Using default values.
if set.HashSize == 0 {
set.HashSize = 1024
}
if set.MaxElem == 0 {
set.MaxElem = 65536
}
if set.HashFamily == "" {
set.HashFamily = ProtocolFamilyIPV4
}
if len(set.HashFamily) != 0 && set.HashFamily != ProtocolFamilyIPV4 && set.HashFamily != ProtocolFamilyIPV6 {
return fmt.Errorf("Currently supported protocol families are: %s and %s, %s is not supported", ProtocolFamilyIPV4, ProtocolFamilyIPV6, set.HashFamily)
}
// Default ipset type is "hash:ip,port"
if len(set.SetType) == 0 {
set.SetType = HashIPPort
}
// Check if setType is supported
if !IsValidIPSetType(set.SetType) {
return fmt.Errorf("Currently supported ipset types are: %v, %s is not supported", ValidIPSetTypes, set.SetType)
}
return runner.createSet(set, ignoreExistErr)
}
// If ignoreExistErr is set to true, then the -exist option of ipset will be specified, ipset ignores the error
// otherwise raised when the same set (setname and create parameters are identical) already exists.
func (runner *runner) createSet(set *IPSet, ignoreExistErr bool) error {
args := []string{"create", set.Name, string(set.SetType)}
if set.SetType == HashIPPortIP || set.SetType == HashIPPort {
args = append(args,
"family", set.HashFamily,
"hashsize", strconv.Itoa(set.HashSize),
"maxelem", strconv.Itoa(set.MaxElem),
)
}
if set.SetType == BitmapPort {
if len(set.PortRange) == 0 {
set.PortRange = DefaultPortRange
}
if !validatePortRange(set.PortRange) {
return fmt.Errorf("invalid port range for %s type ip set: %s, expect: a-b", BitmapPort, set.PortRange)
}
args = append(args, "range", set.PortRange)
}
if ignoreExistErr {
args = append(args, "-exist")
}
if _, err := runner.exec.Command(IPSetCmd, args...).CombinedOutput(); err != nil {
return fmt.Errorf("error creating ipset %s, error: %v", set.Name, err)
}
return nil
}
// AddEntry adds a new entry to the named set.
// If the -exist option is specified, ipset ignores the error otherwise raised when
// the same set (setname and create parameters are identical) already exists.
func (runner *runner) AddEntry(entry string, set string, ignoreExistErr bool) error {
args := []string{"add", set, entry}
if ignoreExistErr {
args = append(args, "-exist")
}
if _, err := runner.exec.Command(IPSetCmd, args...).CombinedOutput(); err != nil {
return fmt.Errorf("error adding entry %s, error: %v", entry, err)
}
return nil
}
// DelEntry is used to delete the specified entry from the set.
func (runner *runner) DelEntry(entry string, set string) error {
if _, err := runner.exec.Command(IPSetCmd, "del", set, entry).CombinedOutput(); err != nil {
return fmt.Errorf("error deleting entry %s: from set: %s, error: %v", entry, set, err)
}
return nil
}
// TestEntry is used to check whether the specified entry is in the set or not.
func (runner *runner) TestEntry(entry string, set string) (bool, error) {
if out, err := runner.exec.Command(IPSetCmd, "test", set, entry).CombinedOutput(); err == nil {
reg, e := regexp.Compile("NOT")
if e == nil && reg.MatchString(string(out)) {
return false, nil
} else if e == nil {
return true, nil
} else {
return false, fmt.Errorf("error testing entry: %s, error: %v", entry, e)
}
} else {
return false, fmt.Errorf("error testing entry %s: %v (%s)", entry, err, out)
}
}
// FlushSet deletes all entries from a named set.
func (runner *runner) FlushSet(set string) error {
if _, err := runner.exec.Command(IPSetCmd, "flush", set).CombinedOutput(); err != nil {
return fmt.Errorf("error flushing set: %s, error: %v", set, err)
}
return nil
}
// DestroySet is used to destroy a named set.
func (runner *runner) DestroySet(set string) error {
if _, err := runner.exec.Command(IPSetCmd, "destroy", set).CombinedOutput(); err != nil {
return fmt.Errorf("error destroying set %s:, error: %v", set, err)
}
return nil
}
// DestroyAllSets is used to destroy all sets.
func (runner *runner) DestroyAllSets() error {
if _, err := runner.exec.Command(IPSetCmd, "destroy").CombinedOutput(); err != nil {
return fmt.Errorf("error destroying all sets, error: %v", err)
}
return nil
}
// ListSets list all set names from kernel
func (runner *runner) ListSets() ([]string, error) {
out, err := runner.exec.Command(IPSetCmd, "list", "-n").CombinedOutput()
if err != nil {
return nil, fmt.Errorf("error listing all sets, error: %v", err)
}
return strings.Split(string(out), "\n"), nil
}
// ListEntries lists all the entries from a named set.
func (runner *runner) ListEntries(set string) ([]string, error) {
if len(set) == 0 {
return nil, fmt.Errorf("set name can't be nil")
}
out, err := runner.exec.Command(IPSetCmd, "list", set).CombinedOutput()
if err != nil {
return nil, fmt.Errorf("error listing set: %s, error: %v", set, err)
}
memberMatcher := regexp.MustCompile(EntryMemberPattern)
list := memberMatcher.ReplaceAllString(string(out[:]), "")
strs := strings.Split(list, "\n")
results := make([]string, 0)
for i := range strs {
if len(strs[i]) > 0 {
results = append(results, strs[i])
}
}
return results, nil
}
// GetVersion returns the version string.
func (runner *runner) GetVersion() (string, error) {
return getIPSetVersionString(runner.exec)
}
// getIPSetVersionString runs "ipset --version" to get the version string
// in the form of "X.Y", i.e "6.19"
func getIPSetVersionString(exec utilexec.Interface) (string, error) {
cmd := exec.Command(IPSetCmd, "--version")
cmd.SetStdin(bytes.NewReader([]byte{}))
bytes, err := cmd.CombinedOutput()
if err != nil {
return "", err
}
versionMatcher := regexp.MustCompile(VersionPattern)
match := versionMatcher.FindStringSubmatch(string(bytes))
if match == nil {
return "", fmt.Errorf("no ipset version found in string: %s", bytes)
}
return match[0], nil
}
func validatePortRange(portRange string) bool {
strs := strings.Split(portRange, "-")
if len(strs) != 2 {
return false
}
for i := range strs {
if _, err := strconv.Atoi(strs[i]); err != nil {
return false
}
}
return true
}
var _ = Interface(&runner{})

View File

@ -0,0 +1,483 @@
/*
Copyright 2017 The Kubernetes 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,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ipset
import (
"reflect"
"testing"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/utils/exec"
fakeexec "k8s.io/utils/exec/testing"
)
func TestCheckIPSetVersion(t *testing.T) {
testCases := []struct {
vstring string
Expect string
Err bool
}{
{"ipset v4.0, protocol version: 4", "v4.0", false},
{"ipset v5.1, protocol version: 5", "v5.1", false},
{"ipset v6.0, protocol version: 6", "v6.0", false},
{"ipset v6.1, protocol version: 6", "v6.1", false},
{"ipset v6.19, protocol version: 6", "v6.19", false},
{"total junk", "", true},
}
for i := range testCases {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
// ipset version response
func() ([]byte, error) { return []byte(testCases[i].vstring), nil },
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
},
}
gotVersion, err := getIPSetVersionString(&fexec)
if (err != nil) != testCases[i].Err {
t.Errorf("Expected error: %v, Got error: %v", testCases[i].Err, err)
}
if err == nil {
if testCases[i].Expect != gotVersion {
t.Errorf("Expected result: %v, Got result: %v", testCases[i].Expect, gotVersion)
}
}
}
}
func TestFlushSet(t *testing.T) {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
// Success
func() ([]byte, error) { return []byte{}, nil },
// Success
func() ([]byte, error) { return []byte{}, nil },
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
},
}
runner := New(&fexec)
// Success.
err := runner.FlushSet("FOOBAR")
if err != nil {
t.Errorf("expected success, got %v", err)
}
if fcmd.CombinedOutputCalls != 1 {
t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls)
}
if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "flush", "FOOBAR") {
t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0])
}
// Flush again
err = runner.FlushSet("FOOBAR")
if err != nil {
t.Errorf("expected success, got %v", err)
}
}
func TestDestroySet(t *testing.T) {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
// Success
func() ([]byte, error) { return []byte{}, nil },
// Failure
func() ([]byte, error) {
return []byte("ipset v6.19: The set with the given name does not exist"), &fakeexec.FakeExitError{Status: 1}
},
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
},
}
runner := New(&fexec)
// Success
err := runner.DestroySet("FOOBAR")
if err != nil {
t.Errorf("expected success, got %v", err)
}
if fcmd.CombinedOutputCalls != 1 {
t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls)
}
if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "destroy", "FOOBAR") {
t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0])
}
// Failure
err = runner.DestroySet("FOOBAR")
if err == nil {
t.Errorf("expected failure, got nil")
}
}
func TestDestroyAllSets(t *testing.T) {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
// Success
func() ([]byte, error) { return []byte{}, nil },
// Success
func() ([]byte, error) { return []byte{}, nil },
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
},
}
runner := New(&fexec)
// Success
err := runner.DestroyAllSets()
if err != nil {
t.Errorf("expected success, got %v", err)
}
if fcmd.CombinedOutputCalls != 1 {
t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls)
}
if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "destroy") {
t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0])
}
// Success
err = runner.DestroyAllSets()
if err != nil {
t.Errorf("Unexpected failure: %v", err)
}
}
func TestCreateSet(t *testing.T) {
testSet := IPSet{
Name: "FOOBAR",
SetType: HashIPPort,
HashFamily: ProtocolFamilyIPV4,
}
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
// Success
func() ([]byte, error) { return []byte{}, nil },
// Success
func() ([]byte, error) { return []byte{}, nil },
// Failure
func() ([]byte, error) {
return []byte("ipset v6.19: Set cannot be created: set with the same name already exists"), &fakeexec.FakeExitError{Status: 1}
},
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
},
}
runner := New(&fexec)
// Create with ignoreExistErr = false, expect success
err := runner.CreateSet(&testSet, false)
if err != nil {
t.Errorf("expected success, got %v", err)
}
if fcmd.CombinedOutputCalls != 1 {
t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls)
}
if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "create", "FOOBAR", "hash:ip,port", "family", "inet", "hashsize", "1024", "maxelem", "65536") {
t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0])
}
// Create with ignoreExistErr = true, expect success
err = runner.CreateSet(&testSet, true)
if err != nil {
t.Errorf("expected success, got %v", err)
}
if fcmd.CombinedOutputCalls != 2 {
t.Errorf("expected 2 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls)
}
if !sets.NewString(fcmd.CombinedOutputLog[1]...).HasAll("ipset", "create", "FOOBAR", "hash:ip,port", "family", "inet", "hashsize", "1024", "maxelem", "65536", "-exist") {
t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[1])
}
// Create with ignoreExistErr = false, expect failure
err = runner.CreateSet(&testSet, false)
if err == nil {
t.Errorf("expected failure, got nil")
}
}
func TestAddEntry(t *testing.T) {
testEntry := &Entry{
IP: "192.168.1.1",
Port: 53,
Protocol: ProtocolUDP,
SetType: HashIPPort,
}
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
// Success
func() ([]byte, error) { return []byte{}, nil },
// Success
func() ([]byte, error) { return []byte{}, nil },
// Failure
func() ([]byte, error) {
return []byte("ipset v6.19: Set cannot be created: set with the same name already exists"), &fakeexec.FakeExitError{Status: 1}
},
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
},
}
runner := New(&fexec)
// Create with ignoreExistErr = false, expect success
err := runner.AddEntry(testEntry.String(), "FOOBAR", false)
if err != nil {
t.Errorf("expected success, got %v", err)
}
if fcmd.CombinedOutputCalls != 1 {
t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls)
}
if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "add", "FOOBAR", "192.168.1.1,udp:53") {
t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0])
}
// Create with ignoreExistErr = true, expect success
err = runner.AddEntry(testEntry.String(), "FOOBAR", true)
if err != nil {
t.Errorf("expected success, got %v", err)
}
if fcmd.CombinedOutputCalls != 2 {
t.Errorf("expected 3 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls)
}
if !sets.NewString(fcmd.CombinedOutputLog[1]...).HasAll("ipset", "add", "FOOBAR", "192.168.1.1,udp:53", "-exist") {
t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[1])
}
// Create with ignoreExistErr = false, expect failure
err = runner.AddEntry(testEntry.String(), "FOOBAR", false)
if err == nil {
t.Errorf("expected failure, got nil")
}
}
func TestDelEntry(t *testing.T) {
// TODO: Test more set type
testEntry := &Entry{
IP: "192.168.1.1",
Port: 53,
Protocol: ProtocolUDP,
SetType: HashIPPort,
}
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
// Success
func() ([]byte, error) { return []byte{}, nil },
// Failure
func() ([]byte, error) {
return []byte("ipset v6.19: Element cannot be deleted from the set: it's not added"), &fakeexec.FakeExitError{Status: 1}
},
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
},
}
runner := New(&fexec)
err := runner.DelEntry(testEntry.String(), "FOOBAR")
if err != nil {
t.Errorf("expected success, got %v", err)
}
if fcmd.CombinedOutputCalls != 1 {
t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls)
}
if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "del", "FOOBAR", "192.168.1.1,udp:53") {
t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0])
}
err = runner.DelEntry(testEntry.String(), "FOOBAR")
if err == nil {
t.Errorf("expected failure, got nil")
}
}
func TestTestEntry(t *testing.T) {
// TODO: IPv6?
testEntry := &Entry{
IP: "10.120.7.100",
Port: 8080,
Protocol: ProtocolTCP,
SetType: HashIPPort,
}
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
// Success
func() ([]byte, error) { return []byte("10.120.7.100,tcp:8080 is in set FOOBAR."), nil },
// Failure
func() ([]byte, error) {
return []byte("192.168.1.3,tcp:8080 is NOT in set FOOBAR."), &fakeexec.FakeExitError{Status: 1}
},
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
},
}
runner := New(&fexec)
// Success
ok, err := runner.TestEntry(testEntry.String(), "FOOBAR")
if err != nil {
t.Errorf("expected success, got %v", err)
}
if fcmd.CombinedOutputCalls != 1 {
t.Errorf("expected 2 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls)
}
if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "test", "FOOBAR", "10.120.7.100,tcp:8080") {
t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0])
}
if !ok {
t.Errorf("expect entry exists in test set, got not")
}
// Failure
ok, err = runner.TestEntry(testEntry.String(), "FOOBAR")
if err == nil || ok {
t.Errorf("expect entry doesn't exist in test set")
}
}
func TestListEntries(t *testing.T) {
output := `Name: foobar
Type: hash:ip,port
Revision: 2
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 16592
References: 0
Members:
192.168.1.2,tcp:8080
192.168.1.1,udp:53`
emptyOutput := `Name: KUBE-NODE-PORT
Type: bitmap:port
Revision: 1
Header: range 0-65535
Size in memory: 524432
References: 1
Members:
`
testCases := []struct {
output string
expected []string
}{
{
output: output,
expected: []string{"192.168.1.2,tcp:8080", "192.168.1.1,udp:53"},
},
{
output: emptyOutput,
expected: []string{},
},
}
for i := range testCases {
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
// Success
func() ([]byte, error) {
return []byte(testCases[i].output), nil
},
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd {
return fakeexec.InitFakeCmd(&fcmd, cmd, args...)
},
},
}
runner := New(&fexec)
// Success
entries, err := runner.ListEntries("foobar")
if err != nil {
t.Errorf("expected success, got: %v", err)
}
if fcmd.CombinedOutputCalls != 1 {
t.Errorf("expected 1 CombinedOutput() calls, got: %d", fcmd.CombinedOutputCalls)
}
if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "list", "foobar") {
t.Errorf("wrong CombinedOutput() log, got: %s", fcmd.CombinedOutputLog[0])
}
if len(entries) != len(testCases[i].expected) {
t.Errorf("expected %d ipset entries, got: %d", len(testCases[i].expected), len(entries))
}
if !reflect.DeepEqual(entries, testCases[i].expected) {
t.Errorf("expected entries: %v, got: %v", testCases[i].expected, entries)
}
}
}
func TestListSets(t *testing.T) {
output := `foo
bar
baz`
expected := []string{"foo", "bar", "baz"}
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
// Success
func() ([]byte, error) { return []byte(output), nil },
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
},
}
runner := New(&fexec)
// Success
list, err := runner.ListSets()
if err != nil {
t.Errorf("expected success, got: %v", err)
}
if fcmd.CombinedOutputCalls != 1 {
t.Errorf("expected 1 CombinedOutput() calls, got: %d", fcmd.CombinedOutputCalls)
}
if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "list", "-n") {
t.Errorf("wrong CombinedOutput() log, got: %s", fcmd.CombinedOutputLog[0])
}
if len(list) != len(expected) {
t.Errorf("expected %d sets, got: %d", len(expected), len(list))
}
if !reflect.DeepEqual(list, expected) {
t.Errorf("expected sets: %v, got: %v", expected, list)
}
}

View File

@ -0,0 +1,23 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["fake.go"],
importpath = "k8s.io/kubernetes/pkg/util/ipset/testing",
visibility = ["//visibility:public"],
deps = ["//pkg/util/ipset:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,83 @@
/*
Copyright 2017 The Kubernetes 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,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package testing
import (
"k8s.io/kubernetes/pkg/util/ipset"
)
// FakeIPSet is a no-op implementation of ipset Interface
type FakeIPSet struct {
Lines []byte
}
// NewFake create a new fake ipset interface.
func NewFake() *FakeIPSet {
return &FakeIPSet{}
}
// GetVersion is part of interface.
func (*FakeIPSet) GetVersion() (string, error) {
return "0.0", nil
}
// FlushSet is part of interface.
func (*FakeIPSet) FlushSet(set string) error {
return nil
}
// DestroySet is part of interface.
func (*FakeIPSet) DestroySet(set string) error {
return nil
}
// DestroyAllSets is part of interface.
func (*FakeIPSet) DestroyAllSets() error {
return nil
}
// CreateSet is part of interface.
func (*FakeIPSet) CreateSet(set *ipset.IPSet, ignoreExistErr bool) error {
return nil
}
// AddEntry is part of interface.
func (*FakeIPSet) AddEntry(entry string, set string, ignoreExistErr bool) error {
return nil
}
// DelEntry is part of interface.
func (*FakeIPSet) DelEntry(entry string, set string) error {
return nil
}
// TestEntry is part of interface.
func (*FakeIPSet) TestEntry(entry string, set string) (bool, error) {
return true, nil
}
// ListEntries is part of interface.
func (*FakeIPSet) ListEntries(set string) ([]string, error) {
return nil, nil
}
// ListSets is part of interface.
func (*FakeIPSet) ListSets() ([]string, error) {
return nil, nil
}
var _ = ipset.Interface(&FakeIPSet{})

70
pkg/util/ipset/types.go Normal file
View File

@ -0,0 +1,70 @@
/*
Copyright 2017 The Kubernetes 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,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ipset
// Type represents the ipset type
type Type string
const (
// HashIPPort represents the `hash:ip,port` type ipset. The hash:ip,port is similar to hash:ip but
// you can store IP address and protocol-port pairs in it. TCP, SCTP, UDP, UDPLITE, ICMP and ICMPv6 are supported
// with port numbers/ICMP(v6) types and other protocol numbers without port information.
HashIPPort Type = "hash:ip,port"
// HashIPPortIP represents the `hash:ip,port,ip` type ipset. The hash:ip,port,ip set type uses a hash to store
// IP address, port number and a second IP address triples. The port number is interpreted together with a
// protocol (default TCP) and zero protocol number cannot be used.
HashIPPortIP Type = "hash:ip,port,ip"
// HashIPPortNet represents the `hash:ip,port,net` type ipset. The hash:ip,port,net set type uses a hash to store IP address, port number and IP network address triples. The port
// number is interpreted together with a protocol (default TCP) and zero protocol number cannot be used. Network address
// with zero prefix size cannot be stored either.
HashIPPortNet Type = "hash:ip,port,net"
// BitmapPort represents the `bitmap:port` type ipset. The bitmap:port set type uses a memory range, where each bit
// represents one TCP/UDP port. A bitmap:port type of set can store up to 65535 ports.
BitmapPort Type = "bitmap:port"
)
// DefaultPortRange defines the default bitmap:port valid port range.
const DefaultPortRange string = "0-65535"
const (
// ProtocolFamilyIPV4 represents IPv4 protocol.
ProtocolFamilyIPV4 = "inet"
// ProtocolFamilyIPV6 represents IPv6 protocol.
ProtocolFamilyIPV6 = "inet6"
// ProtocolTCP represents TCP protocol.
ProtocolTCP = "tcp"
// ProtocolUDP represents UDP protocol.
ProtocolUDP = "udp"
)
// ValidIPSetTypes defines the supported ip set type.
var ValidIPSetTypes = []Type{
HashIPPort,
HashIPPortIP,
BitmapPort,
HashIPPortNet,
}
// IsValidIPSetType checks if the given ipset type is valid.
func IsValidIPSetType(set Type) bool {
for _, valid := range ValidIPSetTypes {
if set == valid {
return true
}
}
return false
}