diff --git a/.github/workflows/lint-go.yaml b/.github/workflows/lint-go.yaml new file mode 100644 index 0000000..229d24f --- /dev/null +++ b/.github/workflows/lint-go.yaml @@ -0,0 +1,22 @@ +name: Go Checks + +on: + pull_request: {} + push: + branches: + - main + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version: '1.21' + cache: false + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.54 \ No newline at end of file diff --git a/.github/workflows/test-go.yaml b/.github/workflows/test-go.yaml new file mode 100644 index 0000000..a1e8f9f --- /dev/null +++ b/.github/workflows/test-go.yaml @@ -0,0 +1,25 @@ +name: Go Tests + +on: + pull_request: {} + push: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: '1.21.x' + - name: Install dependencies + run: go mod tidy + - name: Test Go Source Code with Coverage + run: go test ./... -race -coverprofile=coverage.out -covermode=atomic + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6f42768..8f20677 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,5 @@ .DS_Store *.swp .vscode/ - +.VSCodeCounter out \ No newline at end of file diff --git a/Makefile b/Makefile index b23f584..a3e1ece 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ all: go-gen build ## Build the entire project install-deps: ## Install development dependencies sudo apt install -y protobuf-compiler go install honnef.co/go/tools/cmd/staticcheck@latest + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.55.2 go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2 sudo apt install clang clang-format llvm gcc libbpf-dev libelf-dev make linux-headers-$(uname -r) diff --git a/README.md b/README.md index 41a4923..f3675ba 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,34 @@ # HawkWing The wings which brings your packets to the destination +## Configuration ```yaml --- hawkeye: hostname: hawkeye.hawk.net - port: 1234 + port: 5001 services: - wb.hawk.net: - - intent: high-bandwidth - port: 80 - sid: - - fcbb:bb00:1::2 - - fcbb:bb00:3::2 - - intent: low-bandwidth - port: 8080 - wc.hawk.net: - - intent: high-bandwidth - port: 1433 - sid: - - fcbb:bb00:2::2 - - fcbb:bb00:4::2 -``` - -``` -bpftool net show -bpftool prog tracelog -trace add virtio-input 10 -mount --make-shared /sys/fs/bpf + service1: + domain_name: service1.hawk.net + applications: + - port: 80 + intents: + - intent: sfc + functions: + - function1 + - function2 + sid: + - fcbb:bb00:1::2 + - fcbb:bb00:3::2 + service2: + domain_name: service2.hawk.net + applications: + - port: 80 + intents: + - intent: low-latency + min_value: 10 + max_value: 20 ``` +## Development Network +![Development Network](docs/network.png) diff --git a/docs/network.png b/docs/network.png new file mode 100644 index 0000000..80a6522 Binary files /dev/null and b/docs/network.png differ diff --git a/internal/test/suite.go b/internal/test/suite.go new file mode 100644 index 0000000..3563b48 --- /dev/null +++ b/internal/test/suite.go @@ -0,0 +1,17 @@ +package test + +import ( + "log" + "testing" + + "github.com/hawkv6/hawkwing/internal/config" +) + +const testConfigPath = "../../test_assets/test_config.yaml" + +func SetupTestConfig(tb testing.TB) { + config.GetInstance().SetConfigFile(testConfigPath) + if err := config.Parse(); err != nil { + log.Fatalln(err) + } +} diff --git a/pkg/bpf/bpffs_test.go b/pkg/bpf/bpffs_test.go new file mode 100644 index 0000000..4ea4475 --- /dev/null +++ b/pkg/bpf/bpffs_test.go @@ -0,0 +1,29 @@ +package bpf + +import ( + "fmt" + "testing" +) + +func TestMapToPath(t *testing.T) { + type args struct { + name string + } + tests := []struct { + name string + args args + want string + }{ + {"basic", args{"foo"}, "/sys/fs/bpf/foo"}, + {"slash", args{"/"}, "/sys/fs/bpf"}, + {"empty", args{""}, "/sys/fs/bpf"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := MapToPath(tt.args.name); got != tt.want { + fmt.Println(got) + t.Errorf("MapToPath() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/entities/intent.go b/pkg/entities/intent.go index ad7629c..bbfc50b 100644 --- a/pkg/entities/intent.go +++ b/pkg/entities/intent.go @@ -1,7 +1,7 @@ package entities import ( - "log" + "fmt" "github.com/hawkv6/hawkwing/internal/config" "github.com/hawkv6/hawkwing/pkg/types" @@ -51,7 +51,7 @@ type Intent struct { IntentValues []IntentValue } -func CreateIntentsForServiceApplication(serviceKey string, applicationPort int) []Intent { +func CreateIntentsForServiceApplication(serviceKey string, applicationPort int) ([]Intent, error) { serviceCfg := config.Params.Services[serviceKey] intents := make([]Intent, 0) for _, application := range serviceCfg.Applications { @@ -59,7 +59,7 @@ func CreateIntentsForServiceApplication(serviceKey string, applicationPort int) for _, intent := range application.Intents { intentType, err := types.ParseIntentType(intent.Intent) if err != nil { - log.Fatalf("failed to parse intent type %s: %v", intent.Intent, err) + return nil, fmt.Errorf("failed to parse intent type %s: %v", intent.Intent, err) } intents = append(intents, Intent{ IntentType: intentType, @@ -68,5 +68,5 @@ func CreateIntentsForServiceApplication(serviceKey string, applicationPort int) } } } - return intents + return intents, nil } diff --git a/pkg/entities/intent_test.go b/pkg/entities/intent_test.go new file mode 100644 index 0000000..9dced56 --- /dev/null +++ b/pkg/entities/intent_test.go @@ -0,0 +1,279 @@ +package entities + +import ( + "testing" + + "github.com/hawkv6/hawkwing/internal/config" + "github.com/hawkv6/hawkwing/internal/test" + "github.com/hawkv6/hawkwing/pkg/types" +) + +func TestCreateIntentValueForIntent(t *testing.T) { + type args struct { + intent config.Intent + } + tests := []struct { + name string + args args + want []IntentValue + }{ + { + name: "SFC", + args: args{ + intent: config.Intent{ + Intent: types.IntentTypeSfc.String(), + Functions: []string{ + "function1", + "function2", + }, + }, + }, + want: []IntentValue{ + { + IntentValueType: types.IntentValueTypeSFC, + StringValue: "function1", + }, + { + IntentValueType: types.IntentValueTypeSFC, + StringValue: "function2", + }, + }, + }, + { + name: "FlexAlgo", + args: args{ + intent: config.Intent{ + Intent: types.IntentTypeFlexAlgo.String(), + FlexAlgoNr: 1, + }, + }, + want: []IntentValue{ + { + IntentValueType: types.IntentValueTypeFlexAlgoNr, + NumberValue: 1, + }, + }, + }, + { + name: "HighBandwidth", + args: args{ + intent: config.Intent{ + Intent: types.IntentTypeHighBandwidth.String(), + MinValue: 1, + MaxValue: 2, + }, + }, + want: []IntentValue{ + { + IntentValueType: types.IntentValueTypeMinValue, + NumberValue: 1, + }, + { + IntentValueType: types.IntentValueTypeMaxValue, + NumberValue: 2, + }, + }, + }, + { + name: "LowLatency", + args: args{ + intent: config.Intent{ + Intent: types.IntentTypeLowLatency.String(), + MinValue: 1, + MaxValue: 2, + }, + }, + want: []IntentValue{ + { + IntentValueType: types.IntentValueTypeMinValue, + NumberValue: 1, + }, + { + IntentValueType: types.IntentValueTypeMaxValue, + NumberValue: 2, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + intentValues := CreateIntentValueForIntent(tt.args.intent) + if len(intentValues) != len(tt.want) { + t.Errorf("Expected %d intent values, got %d", len(tt.want), len(intentValues)) + } + for i, intentValue := range intentValues { + if intentValue.IntentValueType != tt.want[i].IntentValueType { + t.Errorf("Expected intent value type %s, got %s", tt.want[i].IntentValueType, intentValue.IntentValueType) + } + if intentValue.NumberValue != tt.want[i].NumberValue { + t.Errorf("Expected number value %d, got %d", tt.want[i].NumberValue, intentValue.NumberValue) + } + if intentValue.StringValue != tt.want[i].StringValue { + t.Errorf("Expected string value %s, got %s", tt.want[i].StringValue, intentValue.StringValue) + } + } + }) + } +} + +func TestCreateIntentsForServiceApplication(t *testing.T) { + test.SetupTestConfig(t) + type args struct { + serviceKey string + applicationPort int + } + + tests := []struct { + name string + args args + want []Intent + }{ + { + name: "SFC", + args: args{ + serviceKey: "service1", + applicationPort: 80, + }, + want: []Intent{ + { + IntentType: types.IntentTypeSfc, + IntentValues: []IntentValue{ + { + IntentValueType: types.IntentValueTypeSFC, + StringValue: "function1", + }, + { + IntentValueType: types.IntentValueTypeSFC, + StringValue: "function2", + }, + }, + }, + }, + }, + { + name: "FlexAlgo", + args: args{ + serviceKey: "service1", + applicationPort: 8080, + }, + want: []Intent{ + { + IntentType: types.IntentTypeFlexAlgo, + IntentValues: []IntentValue{ + { + IntentValueType: types.IntentValueTypeFlexAlgoNr, + NumberValue: 1, + }, + }, + }, + }, + }, + { + name: "HighBandwidth", + args: args{ + serviceKey: "service2", + applicationPort: 1433, + }, + want: []Intent{ + { + IntentType: types.IntentTypeHighBandwidth, + IntentValues: []IntentValue{ + { + IntentValueType: types.IntentValueTypeMinValue, + NumberValue: 1, + }, + { + IntentValueType: types.IntentValueTypeMaxValue, + NumberValue: 2, + }, + }, + }, + }, + }, + { + name: "MultipleIntents", + args: args{ + serviceKey: "service3", + applicationPort: 443, + }, + want: []Intent{ + { + IntentType: types.IntentTypeFlexAlgo, + IntentValues: []IntentValue{ + { + IntentValueType: types.IntentValueTypeFlexAlgoNr, + NumberValue: 1, + }, + }, + }, + { + IntentType: types.IntentTypeLowBandwidth, + IntentValues: []IntentValue{ + { + IntentValueType: types.IntentValueTypeMinValue, + NumberValue: 1, + }, + { + IntentValueType: types.IntentValueTypeMaxValue, + NumberValue: 2, + }, + }, + }, + { + IntentType: types.IntentTypeLowLatency, + IntentValues: []IntentValue{ + { + IntentValueType: types.IntentValueTypeMinValue, + NumberValue: 1, + }, + { + IntentValueType: types.IntentValueTypeMaxValue, + NumberValue: 2, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + intents, err := CreateIntentsForServiceApplication(tt.args.serviceKey, tt.args.applicationPort) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if len(intents) != len(tt.want) { + t.Errorf("Expected %d intents, got %d", len(tt.want), len(intents)) + } + for i, intent := range intents { + if intent.IntentType != tt.want[i].IntentType { + t.Errorf("Expected intent type %s, got %s", tt.want[i].IntentType, intent.IntentType) + } + if len(intent.IntentValues) != len(tt.want[i].IntentValues) { + t.Errorf("Expected %d intent values, got %d", len(tt.want[i].IntentValues), len(intent.IntentValues)) + } + for j, intentValue := range intent.IntentValues { + if intentValue.IntentValueType != tt.want[i].IntentValues[j].IntentValueType { + t.Errorf("Expected intent value type %s, got %s", tt.want[i].IntentValues[j].IntentValueType, intentValue.IntentValueType) + } + if intentValue.NumberValue != tt.want[i].IntentValues[j].NumberValue { + t.Errorf("Expected number value %d, got %d", tt.want[i].IntentValues[j].NumberValue, intentValue.NumberValue) + } + if intentValue.StringValue != tt.want[i].IntentValues[j].StringValue { + t.Errorf("Expected string value %s, got %s", tt.want[i].IntentValues[j].StringValue, intentValue.StringValue) + } + } + } + }) + } + + application0Intent0 := config.Params.Services["service1"].Applications[0].Intents[0] + application0Intent0.Intent = "invalid" + config.Params.Services["service1"].Applications[0].Intents[0] = application0Intent0 + + _, err := CreateIntentsForServiceApplication("service1", 80) + if err == nil { + t.Errorf("Expected error, got nil") + } +} diff --git a/pkg/entities/path_request.go b/pkg/entities/path_request.go index 1e9b920..9c8a657 100644 --- a/pkg/entities/path_request.go +++ b/pkg/entities/path_request.go @@ -40,15 +40,18 @@ func (pr *PathRequest) Marshal() *api.PathRequest { } } -func CreatePathRequestsForService(serviceKey string) []PathRequest { +func CreatePathRequestsForService(serviceKey string) ([]PathRequest, error) { serviceCfg := config.Params.Services[serviceKey] pathRequests := make([]PathRequest, 0, len(serviceCfg.Ipv6Addresses)) for _, application := range serviceCfg.Applications { - applicationIntents := CreateIntentsForServiceApplication(serviceKey, application.Port) + applicationIntents, err := CreateIntentsForServiceApplication(serviceKey, application.Port) + if err != nil { + return nil, err + } for _, ipv6Addr := range serviceCfg.Ipv6Addresses { pathRequests = append(pathRequests, NewPathRequest(ipv6Addr, applicationIntents)) } } - return pathRequests + return pathRequests, nil } diff --git a/pkg/entities/path_request_test.go b/pkg/entities/path_request_test.go new file mode 100644 index 0000000..2912918 --- /dev/null +++ b/pkg/entities/path_request_test.go @@ -0,0 +1,257 @@ +package entities + +import ( + "reflect" + "testing" + + "github.com/hawkv6/hawkwing/internal/test" + "github.com/hawkv6/hawkwing/pkg/api" + "github.com/hawkv6/hawkwing/pkg/types" +) + +func TestNewPathRequest(t *testing.T) { + type args struct { + ipv6daddr string + intents []Intent + } + tests := []struct { + name string + args args + want PathRequest + }{ + { + name: "TestNewPathRequest", + args: args{ + ipv6daddr: "2001:db8::1", + intents: []Intent{ + { + IntentType: types.IntentTypeSfc, + IntentValues: []IntentValue{ + { + IntentValueType: types.IntentValueTypeSFC, + StringValue: "function1", + }, + { + IntentValueType: types.IntentValueTypeSFC, + StringValue: "function2", + }, + }, + }, + }, + }, + want: PathRequest{ + Ipv6DestinationAddress: "2001:db8::1", + Intents: []Intent{ + { + IntentType: types.IntentTypeSfc, + IntentValues: []IntentValue{ + { + IntentValueType: types.IntentValueTypeSFC, + StringValue: "function1", + }, + { + IntentValueType: types.IntentValueTypeSFC, + StringValue: "function2", + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + if got := NewPathRequest(tt.args.ipv6daddr, tt.args.intents); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewPathRequest() = %v, want %v", got, tt.want) + } + } + +} + +func TestPathRequest_Marshal(t *testing.T) { + pr := NewPathRequest("2001:db8::1", []Intent{ + { + IntentType: types.IntentTypeSfc, + IntentValues: []IntentValue{ + { + IntentValueType: types.IntentValueTypeSFC, + StringValue: "function1", + }, + { + IntentValueType: types.IntentValueTypeSFC, + StringValue: "function2", + }, + }, + }, + }) + if pr.Marshal() == nil { + t.Errorf("PathRequest.Marshal() = nil") + } + if pr.Marshal().Ipv6DestinationAddress != "2001:db8::1" { + t.Errorf("PathRequest.Marshal() = %v", pr.Marshal().Ipv6DestinationAddress) + } + if len(pr.Marshal().Intents) != 1 { + t.Errorf("PathRequest.Marshal() = %v", len(pr.Marshal().Intents)) + } + if pr.Marshal().Intents[0].Type != api.IntentType(types.IntentTypeSfc) { + t.Errorf("PathRequest.Marshal() = %v", pr.Marshal().Intents[0].Type) + } + if len(pr.Marshal().Intents[0].Values) != 2 { + t.Errorf("PathRequest.Marshal() = %v", len(pr.Marshal().Intents[0].Values)) + } + if pr.Marshal().Intents[0].Values[0].Type != api.ValueType(types.IntentValueTypeSFC) { + t.Errorf("PathRequest.Marshal() = %v", pr.Marshal().Intents[0].Values[0].Type) + } + if pr.Marshal().Intents[0].Values[1].Type != api.ValueType(types.IntentValueTypeSFC) { + t.Errorf("PathRequest.Marshal() = %v", pr.Marshal().Intents[0].Values[1].Type) + } + +} + +func TestCreatePathRequestsForService(t *testing.T) { + test.SetupTestConfig(t) + + type args struct { + serviceKey string + } + tests := []struct { + name string + args args + want []PathRequest + }{ + { + name: "Mutliple intents", + args: args{ + serviceKey: "service3", + }, + want: []PathRequest{ + { + Ipv6DestinationAddress: "fcbb:cc00:4::a", + Intents: []Intent{ + { + IntentType: types.IntentTypeFlexAlgo, + IntentValues: []IntentValue{ + { + IntentValueType: types.IntentValueTypeFlexAlgoNr, + NumberValue: 1, + }, + }, + }, + { + IntentType: types.IntentTypeLowBandwidth, + IntentValues: []IntentValue{ + { + IntentValueType: types.IntentValueTypeMinValue, + NumberValue: 1, + }, + { + IntentValueType: types.IntentValueTypeMaxValue, + NumberValue: 2, + }, + }, + }, + { + IntentType: types.IntentTypeLowLatency, + IntentValues: []IntentValue{ + { + IntentValueType: types.IntentValueTypeMinValue, + NumberValue: 1, + }, + { + IntentValueType: types.IntentValueTypeMaxValue, + NumberValue: 2, + }, + }, + }, + }, + }, + { + Ipv6DestinationAddress: "fcbb:cc00:4::b", + Intents: []Intent{ + { + IntentType: types.IntentTypeFlexAlgo, + IntentValues: []IntentValue{ + { + IntentValueType: types.IntentValueTypeFlexAlgoNr, + NumberValue: 1, + }, + }, + }, + { + IntentType: types.IntentTypeLowBandwidth, + IntentValues: []IntentValue{ + { + IntentValueType: types.IntentValueTypeMinValue, + NumberValue: 1, + }, + { + IntentValueType: types.IntentValueTypeMaxValue, + NumberValue: 2, + }, + }, + }, + { + IntentType: types.IntentTypeLowLatency, + IntentValues: []IntentValue{ + { + IntentValueType: types.IntentValueTypeMinValue, + NumberValue: 1, + }, + { + IntentValueType: types.IntentValueTypeMaxValue, + NumberValue: 2, + }, + }, + }, + }, + }, + { + Ipv6DestinationAddress: "fcbb:cc00:4::c", + Intents: []Intent{ + { + IntentType: types.IntentTypeFlexAlgo, + IntentValues: []IntentValue{ + { + IntentValueType: types.IntentValueTypeFlexAlgoNr, + NumberValue: 1, + }, + }, + }, + { + IntentType: types.IntentTypeLowBandwidth, + IntentValues: []IntentValue{ + { + IntentValueType: types.IntentValueTypeMinValue, + NumberValue: 1, + }, + { + IntentValueType: types.IntentValueTypeMaxValue, + NumberValue: 2, + }, + }, + }, + { + IntentType: types.IntentTypeLowLatency, + IntentValues: []IntentValue{ + { + IntentValueType: types.IntentValueTypeMinValue, + NumberValue: 1, + }, + { + IntentValueType: types.IntentValueTypeMaxValue, + NumberValue: 2, + }, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + if got, err := CreatePathRequestsForService(tt.args.serviceKey); !reflect.DeepEqual(got, tt.want) { + t.Errorf("CreatePathRequestsForService() = %v, want %v", got, tt.want) + } else if err != nil { + t.Errorf("CreatePathRequestsForService() = %v", err) + } + } +} diff --git a/pkg/entities/path_result.go b/pkg/entities/path_result.go index 8e3cab1..e905afb 100644 --- a/pkg/entities/path_result.go +++ b/pkg/entities/path_result.go @@ -19,29 +19,6 @@ func NewPathResult(ipv6daddr string, intents []Intent, ipv6SidAddresses []string } } -func (pr *PathResult) Marshal() *api.PathResult { - intents := make([]*api.Intent, 0, len(pr.Intents)) - for _, intent := range pr.Intents { - intentValues := make([]*api.Value, 0, len(intent.IntentValues)) - for _, val := range intent.IntentValues { - intentValues = append(intentValues, &api.Value{ - Type: api.ValueType(val.IntentValueType), - NumberValue: &val.NumberValue, - StringValue: &val.StringValue, - }) - } - intents = append(intents, &api.Intent{ - Type: api.IntentType(intent.IntentType), - Values: intentValues, - }) - } - return &api.PathResult{ - Ipv6DestinationAddress: pr.Ipv6DestinationAddress, - Intents: intents, - Ipv6SidAddresses: pr.Ipv6SidAddresses, - } -} - func UnmarshalPathResult(pr *api.PathResult) *PathResult { intents := make([]Intent, 0, len(pr.Intents)) for _, intent := range pr.Intents { diff --git a/pkg/entities/path_result_test.go b/pkg/entities/path_result_test.go new file mode 100644 index 0000000..732522c --- /dev/null +++ b/pkg/entities/path_result_test.go @@ -0,0 +1,74 @@ +package entities + +import ( + "reflect" + "testing" + + "github.com/hawkv6/hawkwing/pkg/types" +) + +func TestNewPathResult(t *testing.T) { + type args struct { + ipv6daddr string + intents []Intent + ipv6SidAddresses []string + } + tests := []struct { + name string + args args + want *PathResult + }{ + { + name: "TestNewPathResult", + args: args{ + ipv6daddr: "2001:db8::1", + intents: []Intent{ + { + IntentType: types.IntentTypeSfc, + IntentValues: []IntentValue{ + { + IntentValueType: types.IntentValueTypeSFC, + StringValue: "function1", + }, + { + IntentValueType: types.IntentValueTypeSFC, + StringValue: "function2", + }, + }, + }, + }, + ipv6SidAddresses: []string{ + "2001:db8::2", + "2001:db8::3", + }, + }, + want: &PathResult{ + Ipv6DestinationAddress: "2001:db8::1", + Intents: []Intent{ + { + IntentType: types.IntentTypeSfc, + IntentValues: []IntentValue{ + { + IntentValueType: types.IntentValueTypeSFC, + StringValue: "function1", + }, + { + IntentValueType: types.IntentValueTypeSFC, + StringValue: "function2", + }, + }, + }, + }, + Ipv6SidAddresses: []string{ + "2001:db8::2", + "2001:db8::3", + }, + }, + }, + } + for _, tt := range tests { + if got := NewPathResult(tt.args.ipv6daddr, tt.args.intents, tt.args.ipv6SidAddresses); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewPathResult() = %v, want %v", got, tt.want) + } + } +} diff --git a/pkg/linker/netlink_test.go b/pkg/linker/netlink_test.go new file mode 100644 index 0000000..ba1b443 --- /dev/null +++ b/pkg/linker/netlink_test.go @@ -0,0 +1,45 @@ +package linker + +import ( + "testing" + + "github.com/vishvananda/netlink" +) + +func TestDirectionToParentDisc(t *testing.T) { + type args struct { + direction string + } + tests := []struct { + name string + args args + want uint32 + }{ + { + name: "ingress", + args: args{ + direction: "ingress", + }, + want: netlink.HANDLE_MIN_INGRESS, + }, + { + name: "egress", + args: args{ + direction: "egress", + }, + want: netlink.HANDLE_MIN_EGRESS, + }, + { + name: "false", + args: args{ + direction: "test", + }, + want: 0, + }, + } + for _, tt := range tests { + if got := directionToParentDisc(tt.args.direction); got != tt.want { + t.Errorf("%q. directionToParentDisc() = %v, want %v", tt.name, got, tt.want) + } + } +} diff --git a/pkg/maps/common.go b/pkg/maps/common.go index 6258763..c77979a 100644 --- a/pkg/maps/common.go +++ b/pkg/maps/common.go @@ -44,7 +44,6 @@ func FormatDNSName(domain string) ([256]byte, error) { // Append zero byte to indicate end of the domain name result[offset] = 0 - offset++ return result, nil } diff --git a/pkg/maps/map_test.go b/pkg/maps/map_test.go new file mode 100644 index 0000000..dec7ef0 --- /dev/null +++ b/pkg/maps/map_test.go @@ -0,0 +1,34 @@ +package maps + +import ( + "testing" + + "github.com/cilium/ebpf" +) + +func TestNewMap(t *testing.T) { + spec := &ebpf.MapSpec{ + Name: "test", + Type: ebpf.Array, + KeySize: 4, + ValueSize: 4, + } + m := NewMap(spec) + if m == nil { + t.Error("NewMap() should not return nil") + } +} + +func TestSetPath(t *testing.T) { + spec := &ebpf.MapSpec{ + Name: "test", + Type: ebpf.Array, + KeySize: 4, + ValueSize: 4, + } + m := NewMap(spec) + m.setPath() + if m.path != "/sys/fs/bpf" { + t.Error("setPath() should set path to /sys/fs/bpf/test") + } +} diff --git a/pkg/messaging/adapter.go b/pkg/messaging/adapter.go index 0777db4..d1f3458 100644 --- a/pkg/messaging/adapter.go +++ b/pkg/messaging/adapter.go @@ -15,10 +15,6 @@ func NewMessagingAdapter(messagingChannels *MessagingChannels, adapterChannels * } func (a *MessagingAdapter) Start() { - a.HandleIntent() -} - -func (a *MessagingAdapter) HandleIntent() { go func() { for { intentRequest := <-a.adapterChannels.ChAdapterIntentRequest diff --git a/pkg/messaging/adapter_test.go b/pkg/messaging/adapter_test.go new file mode 100644 index 0000000..5b07e9b --- /dev/null +++ b/pkg/messaging/adapter_test.go @@ -0,0 +1,66 @@ +package messaging + +import ( + "fmt" + "reflect" + "testing" + "time" + + "github.com/hawkv6/hawkwing/internal/test" + "github.com/hawkv6/hawkwing/pkg/api" + "github.com/hawkv6/hawkwing/pkg/entities" +) + +func TestNewMessagingAdapter(t *testing.T) { + mc := NewMessagingChannels() + ac := NewAdapterChannels() + ma := NewMessagingAdapter(mc, ac) + if ma == nil { + t.Errorf("NewMessagingAdapter() = %v, want %v", ma, "not nil") + } + if ma.messagingChannels != mc { + t.Errorf("NewMessagingAdapter() = %v, want %v", ma.messagingChannels, mc) + } + if ma.adapterChannels != ac { + t.Errorf("NewMessagingAdapter() = %v, want %v", ma.adapterChannels, ac) + } +} + +func TestMessagingAdapter_Start(t *testing.T) { + test.SetupTestConfig(t) + mc := NewMessagingChannels() + ac := NewAdapterChannels() + ma := NewMessagingAdapter(mc, ac) + + mockPathRequests, _ := entities.CreatePathRequestsForService("service3") + fmt.Println(mockPathRequests) + + mockPathResult := api.PathResult{ + Ipv6DestinationAddress: "fcbb:cc00:4::a", + Ipv6SidAddresses: []string{ + "2001:db8:0:1::1", + }, + } + + ma.Start() + + ac.ChAdapterIntentRequest <- &mockPathRequests[0] + select { + case receivedRequest := <-mc.ChMessageIntentRequest: + if reflect.DeepEqual(receivedRequest, mockPathRequests[0]) { + t.Errorf("receivedRequest = %v, want %v", receivedRequest, mockPathRequests[0]) + } + case <-time.After(time.Second * 1): + t.Error("Timeout: did not receive message on ChMessageIntentRequest") + } + + mc.ChMessageIntentResponse <- &mockPathResult + select { + case receivedResponse := <-ac.ChAdapterIntentResponse: + if receivedResponse == nil { + t.Error("receivedResponse = nil, want not nil") + } + case <-time.After(time.Second * 1): + t.Error("Timeout: did not receive message on ChAdapterIntentResponse") + } +} diff --git a/pkg/messaging/channels_test.go b/pkg/messaging/channels_test.go new file mode 100644 index 0000000..66db27d --- /dev/null +++ b/pkg/messaging/channels_test.go @@ -0,0 +1,31 @@ +package messaging + +import ( + "testing" +) + +func TestNewMessagingChannels(t *testing.T) { + mc := NewMessagingChannels() + if mc == nil { + t.Errorf("NewMessagingChannels() = %v, want %v", mc, "not nil") + } + if mc.ChMessageIntentRequest == nil { + t.Errorf("NewMessagingChannels() = %v, want %v", mc.ChMessageIntentRequest, "not nil") + } + if mc.ChMessageIntentResponse == nil { + t.Errorf("NewMessagingChannels() = %v, want %v", mc.ChMessageIntentResponse, "not nil") + } +} + +func TestNewAdapterChannels(t *testing.T) { + ac := NewAdapterChannels() + if ac == nil { + t.Errorf("NewAdapterChannels() = %v, want %v", ac, "not nil") + } + if ac.ChAdapterIntentRequest == nil { + t.Errorf("NewAdapterChannels() = %v, want %v", ac.ChAdapterIntentRequest, "not nil") + } + if ac.ChAdapterIntentResponse == nil { + t.Errorf("NewAdapterChannels() = %v, want %v", ac.ChAdapterIntentResponse, "not nil") + } +} diff --git a/pkg/messaging/client_test.go b/pkg/messaging/client_test.go new file mode 100644 index 0000000..a3a6cc1 --- /dev/null +++ b/pkg/messaging/client_test.go @@ -0,0 +1,14 @@ +package messaging + +import "testing" + +func TestNewMessagingClient(t *testing.T) { + mc := NewMessagingChannels() + m := NewMessagingClient(mc) + if m == nil { + t.Errorf("NewMessagingClient() = %v, want %v", m, "not nil") + } + if m.messagingChannels != mc { + t.Errorf("NewMessagingClient() = %v, want %v", m.messagingChannels, mc) + } +} diff --git a/pkg/syncer/resolver.go b/pkg/syncer/resolver.go index 3e1c31f..631dc53 100644 --- a/pkg/syncer/resolver.go +++ b/pkg/syncer/resolver.go @@ -56,7 +56,10 @@ func (rs *ResolverService) ProcessConfig(ctx context.Context) error { serviceCfg.Ipv6Addresses = ipv6AddrStrs config.Params.Services[key] = serviceCfg - pathRequests := entities.CreatePathRequestsForService(key) + pathRequests, err := entities.CreatePathRequestsForService(key) + if err != nil { + return err + } for _, pathRequest := range pathRequests { pr := pathRequest rs.reqChan <- &pr diff --git a/pkg/syncer/syncer.go b/pkg/syncer/syncer.go index 4a1ee4b..2219396 100644 --- a/pkg/syncer/syncer.go +++ b/pkg/syncer/syncer.go @@ -38,7 +38,10 @@ func (s *Syncer) Start() { func (s *Syncer) FetchAll() { log.Printf("fetching all needed intent details") for key := range config.Params.Services { - pathRequests := entities.CreatePathRequestsForService(key) + pathRequests, err := entities.CreatePathRequestsForService(key) + if err != nil { + log.Fatalf("could not create path requests for service %s: %v", key, err) + } for _, pathRequest := range pathRequests { pr := pathRequest s.reqChan <- &pr diff --git a/pkg/types/types_test.go b/pkg/types/types_test.go new file mode 100644 index 0000000..8e49103 --- /dev/null +++ b/pkg/types/types_test.go @@ -0,0 +1,348 @@ +package types + +import "testing" + +func TestIntentValueTypeToString(t *testing.T) { + type args struct { + ivt IntentValueType + } + tests := []struct { + name string + args args + want string + }{ + { + name: "IntentValueTypeMinValue", + args: args{ + ivt: IntentValueTypeMinValue, + }, + want: "min-value", + }, + { + name: "IntentValueTypeMaxValue", + args: args{ + ivt: IntentValueTypeMaxValue, + }, + want: "max-value", + }, + { + name: "IntentValueTypeSFC", + args: args{ + ivt: IntentValueTypeSFC, + }, + want: "sfc", + }, + { + name: "IntentValueTypeFlexAlgoNr", + args: args{ + ivt: IntentValueTypeFlexAlgoNr, + }, + want: "flex-algo-nr", + }, + { + name: "IntentValueTypeUnspecified", + args: args{ + ivt: IntentValueTypeUnspecified, + }, + want: "unspecified", + }, + { + name: "IntentValueTypeInvalid", + args: args{ + ivt: IntentValueType(100), + }, + want: "unspecified", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.args.ivt.String(); got != tt.want { + t.Errorf("IntentValueType.String() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParseIntentValueType(t *testing.T) { + type args struct { + s string + } + tests := []struct { + name string + args args + want IntentValueType + wantErr bool + }{ + { + name: "IntentValueTypeMinValue", + args: args{ + s: "min-value", + }, + want: IntentValueTypeMinValue, + wantErr: false, + }, + { + name: "IntentValueTypeMaxValue", + args: args{ + s: "max-value", + }, + want: IntentValueTypeMaxValue, + wantErr: false, + }, + { + name: "IntentValueTypeSFC", + args: args{ + s: "sfc", + }, + want: IntentValueTypeSFC, + wantErr: false, + }, + { + name: "IntentValueTypeFlexAlgoNr", + args: args{ + s: "flex-algo-nr", + }, + want: IntentValueTypeFlexAlgoNr, + wantErr: false, + }, + { + name: "IntentValueTypeUnspecified", + args: args{ + s: "unspecified", + }, + want: IntentValueTypeUnspecified, + wantErr: false, + }, + { + name: "IntentValueTypeInvalid", + args: args{ + s: "invalid", + }, + want: IntentValueTypeUnspecified, + wantErr: true, + }, + { + name: "IntentValueTypeEmpty", + args: args{ + s: "", + }, + want: IntentValueTypeUnspecified, + wantErr: true, + }, + { + name: "IntentValueTypeSpace", + args: args{ + s: " ", + }, + want: IntentValueTypeUnspecified, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseIntentValueType(tt.args.s) + if (err != nil) != tt.wantErr { + t.Errorf("ParseIntentValueType() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want && err == nil { + t.Errorf("ParseIntentValueType() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIntentTypeToString(t *testing.T) { + type args struct { + it IntentType + } + tests := []struct { + name string + args args + want string + }{ + { + name: "IntentTypeHighBandwidth", + args: args{ + it: IntentTypeHighBandwidth, + }, + want: "high-bandwidth", + }, + { + name: "IntentTypeLowBandwidth", + args: args{ + it: IntentTypeLowBandwidth, + }, + want: "low-bandwidth", + }, + { + name: "IntentTypeLowLatency", + args: args{ + it: IntentTypeLowLatency, + }, + want: "low-latency", + }, + { + name: "IntentTypeLowPacketLoss", + args: args{ + it: IntentTypeLowPacketLoss, + }, + want: "low-packet-loss", + }, + { + name: "IntentTypeLowJitter", + args: args{ + it: IntentTypeLowJitter, + }, + want: "low-jitter", + }, + { + name: "IntentTypeFlexAlgo", + args: args{ + it: IntentTypeFlexAlgo, + }, + want: "flex-algo", + }, + { + name: "IntentTypeSfc", + args: args{ + it: IntentTypeSfc, + }, + want: "sfc", + }, + { + name: "IntentTypeUnspecified", + args: args{ + it: IntentTypeUnspecified, + }, + want: "unspecified", + }, + { + name: "IntentTypeInvalid", + args: args{ + it: IntentType(100), + }, + want: "unspecified", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.args.it.String(); got != tt.want { + t.Errorf("IntentType.String() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParseIntentType(t *testing.T) { + type args struct { + s string + } + tests := []struct { + name string + args args + want IntentType + wantErr bool + }{ + { + name: "IntentTypeHighBandwidth", + args: args{ + s: "high-bandwidth", + }, + want: IntentTypeHighBandwidth, + wantErr: false, + }, + { + name: "IntentTypeLowBandwidth", + args: args{ + s: "low-bandwidth", + }, + want: IntentTypeLowBandwidth, + wantErr: false, + }, + { + name: "IntentTypeLowLatency", + args: args{ + s: "low-latency", + }, + want: IntentTypeLowLatency, + wantErr: false, + }, + { + name: "IntentTypeLowPacketLoss", + args: args{ + s: "low-packet-loss", + }, + want: IntentTypeLowPacketLoss, + wantErr: false, + }, + { + name: "IntentTypeLowJitter", + args: args{ + s: "low-jitter", + }, + want: IntentTypeLowJitter, + wantErr: false, + }, + { + name: "IntentTypeFlexAlgo", + args: args{ + s: "flex-algo", + }, + want: IntentTypeFlexAlgo, + wantErr: false, + }, + { + name: "IntentTypeSfc", + args: args{ + s: "sfc", + }, + want: IntentTypeSfc, + wantErr: false, + }, + { + name: "IntentTypeUnspecified", + args: args{ + s: "unspecified", + }, + want: IntentTypeUnspecified, + wantErr: false, + }, + { + name: "IntentTypeInvalid", + args: args{ + s: "invalid", + }, + want: IntentTypeUnspecified, + wantErr: true, + }, + { + name: "IntentTypeEmpty", + args: args{ + s: "", + }, + want: IntentTypeUnspecified, + wantErr: true, + }, + { + name: "IntentTypeSpace", + args: args{ + s: " ", + }, + want: IntentTypeUnspecified, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseIntentType(tt.args.s) + if (err != nil) != tt.wantErr { + t.Errorf("ParseIntentType() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want && err == nil { + t.Errorf("ParseIntentType() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/test_assets/test_config.yaml b/test_assets/test_config.yaml new file mode 100644 index 0000000..d6e3849 --- /dev/null +++ b/test_assets/test_config.yaml @@ -0,0 +1,48 @@ +--- +hawkeye: + hostname: fcbb:cc00:5::f + port: 5001 +services: + service1: + domain_name: service1.com + applications: + - port: 80 + sid: + - fcbb:bb00:1::2 + - fcbb:bb00:2::2 + intents: + - intent: sfc + functions: + - function1 + - function2 + - port: 8080 + intents: + - intent: flex-algo + flex_algo_number: 1 + service2: + domain_name: service2.com + applications: + - port: 1433 + sid: + - fcbb:bb00:2::2 + - fcbb:bb00:3::2 + intents: + - intent: high-bandwidth + min_value: 1 + max_value: 2 + service3: + ipv6_addresses: + - fcbb:cc00:4::a + - fcbb:cc00:4::b + - fcbb:cc00:4::c + applications: + - port: 443 + intents: + - intent: flex-algo + flex_algo_number: 1 + - intent: low-bandwidth + min_value: 1 + max_value: 2 + - intent: low-latency + min_value: 1 + max_value: 2 \ No newline at end of file