Skip to content

Commit

Permalink
go, api: Create a structure API
Browse files Browse the repository at this point in the history
At some scenarios is useful to have a "structured" version of the
network_state, this change creates a golang api tested toward examples
and stated used at integration tests it also add the kubebuilder tags to
be k8s CRD friendly, the api is created using a code generator writen in
rust that use the crate "syn" to traverse the rust ast of specific files
that contains the nmstate rust interface.

Signed-off-by: Enrique Llorente <ellorent@redhat.com>
  • Loading branch information
qinqon committed Jan 18, 2024
1 parent b0010c0 commit e6965f2
Show file tree
Hide file tree
Showing 35 changed files with 11,303 additions and 10 deletions.
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ jobs:
run: sudo .github/workflows/run_test.sh ${{ matrix.job_type }}

- uses: actions/upload-artifact@v3
if: success() || failure()
with:
name: nmstate-test-junit-artifact-${{ matrix.job_type }}
path: junit.*xml
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,9 @@ doc/nmstate-autoconf.8
nmstate-*.tar.gz
nmstate-*.tar.gz.asc
/tags
.states
.k8s

api.test
crd.test
rust/src/go/nmstate/file.txt
12 changes: 10 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,14 @@ clib_check: $(CLIB_SO_DEV_DEBUG) $(CLIB_HEADER)
$(TMPDIR)/nmpolicy_yaml_test 1>/dev/null
rm -rf $(TMPDIR)

.PHONY: go_generate
go_generate:
cd rust/src/go/nmstate; go generate
make -C rust/src/go/api/v2 generate
make -C rust/src/go/crd/v2 generate

.PHONY: go_check
go_check: $(CLIB_SO_DEV_DEBUG) $(CLIB_HEADER)
go_check: $(CLIB_SO_DEV_DEBUG) $(CLIB_HEADER) go_generate
$(eval TMPDIR := $(shell mktemp -d))
cp $(CLIB_SO_DEV_DEBUG) $(TMPDIR)/$(CLIB_SO_FULL)
ln -sfv $(CLIB_SO_FULL) $(TMPDIR)/$(CLIB_SO_MAN)
Expand All @@ -229,8 +235,10 @@ go_check: $(CLIB_SO_DEV_DEBUG) $(CLIB_HEADER)
cd rust/src/go/nmstate; LD_LIBRARY_PATH=$(TMPDIR) \
CGO_CFLAGS="-I$(TMPDIR)" \
CGO_LDFLAGS="-L$(TMPDIR)" \
go test $(WHAT)
go test -v $(WHAT)
rm -rf $(TMPDIR)
make -C rust/src/go/api/v2 build-test check-examples
make -C rust/src/go/crd/v2 build-test check-examples

rust_check:
cd rust; cargo test -- --test-threads=1 --show-output;
Expand Down
9 changes: 9 additions & 0 deletions automation/check-gen.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash

set -xe

if [[ -n "$(git status --porcelain)" ]]; then
echo "Git missing generated files"
git status --porcelain
exit 1
fi
5 changes: 5 additions & 0 deletions automation/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ function run_tests {
"support yet"
else
exec_cmd "make go_check"
exec_cmd "automation/check-gen.sh"
fi
fi

Expand All @@ -96,6 +97,7 @@ function run_tests {
pytest \
$PYTEST_OPTIONS \
--junitxml=junit.integ.xml \
--go-checker \
tests/integration \
${nmstate_pytest_extra_args}"
fi
Expand All @@ -106,6 +108,7 @@ function run_tests {
pytest \
$PYTEST_OPTIONS \
--junitxml=junit.integ_tier1.xml \
--go-checker \
-m tier1 \
tests/integration \
${nmstate_pytest_extra_args}"
Expand All @@ -117,6 +120,7 @@ function run_tests {
pytest \
$PYTEST_OPTIONS \
--junitxml=junit.integ_tier2.xml \
--go-checker \
-m tier2 \
tests/integration \
${nmstate_pytest_extra_args}"
Expand All @@ -129,6 +133,7 @@ function run_tests {
pytest \
$PYTEST_OPTIONS \
--junitxml=junit.integ_slow.xml \
--go-checker \
-m slow --runslow \
tests/integration \
${nmstate_pytest_extra_args}"
Expand Down
1 change: 0 additions & 1 deletion examples/dns_remove.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
---
dns-resolver:
config: {}
interfaces: []
1 change: 1 addition & 0 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"src/cli",
"src/clib",
"src/lib",
"src/go/apigen",
]

[workspace.metadata.vendor-filter]
Expand Down
16 changes: 16 additions & 0 deletions rust/src/go/api/v2/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
JUNIT_DIR ?= /tmp/
GO_JUNIT_REPORT=$(shell go env GOPATH)/bin/go-junit-report -set-exit-code

build-test:
go install github.com/jstemmer/go-junit-report/v2@latest
go test -c
check-examples:
go test -v -run TestAPI/examples 2>&1 | $(GO_JUNIT_REPORT) -set-exit-code -iocopy -out $(JUNIT_DIR)/junit.go-api.examples.xml
check-integration:
go test -v -run TestAPI/integration 2>&1 | $(GO_JUNIT_REPORT) -set-exit-code -iocopy -out $(JUNIT_DIR)/junit.go-api.integration.xml
check:
go test -v 2>&1 | $(GO_JUNIT_REPORT) -set-exit-code -iocopy -out $(JUNIT_DIR)/junit.go-api.xml
generate:
go generate
lint:
go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2 run
16 changes: 16 additions & 0 deletions rust/src/go/api/v2/boilerplate.go.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
Copyright The NMState 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.
*/

3 changes: 3 additions & 0 deletions rust/src/go/api/v2/controller-gen.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash -e

GOFLAGS=-mod=mod go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.14.0 $@
208 changes: 208 additions & 0 deletions rust/src/go/api/v2/encoding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
//go:generate cargo run --bin nmstate-go-apigen ../../../lib zz_generated.types.go
//go:generate ./controller-gen.sh object:headerFile="boilerplate.go.txt" paths="."
package v2

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"strings"
)

func replaceDeprecatedNames(data []byte) []byte {
data = []byte(strings.ReplaceAll(string(data), `"slaves"`, `"port"`))
data = []byte(strings.ReplaceAll(string(data), "slaves:", "port:"))
return data
}

func (i *Interface) UnmarshalJSON(data []byte) error {
type InterfaceInternal Interface
var interfaceInternal InterfaceInternal
if err := strictDecoder(replaceDeprecatedNames(data)).Decode(&interfaceInternal); err != nil {
return err
}
*i = Interface(interfaceInternal)
return nil
}

func (o *BridgeOptions) UnmarshalJSON(data []byte) error {
linuxBridgeOptions := LinuxBridgeOptions{}
ovsBridgeOptions := OvsBridgeOptions{}

var linuxErr, ovsErr error
if linuxErr = strictDecoder(data).Decode(&linuxBridgeOptions); linuxErr == nil {
o.LinuxBridgeOptions = &linuxBridgeOptions
} else if ovsErr = strictDecoder(data).Decode(&ovsBridgeOptions); ovsErr == nil {
o.OvsBridgeOptions = &ovsBridgeOptions
} else {
return errors.Join(linuxErr, ovsErr)
}
return nil
}

func (o BridgeOptions) MarshalJSON() ([]byte, error) {
if o.LinuxBridgeOptions != nil {
return json.Marshal(o.LinuxBridgeOptions)
}
if o.OvsBridgeOptions != nil {
return json.Marshal(o.OvsBridgeOptions)
}
return nil, nil
}

func (o *BridgePortConfig) UnmarshalJSON(data []byte) error {
linuxBridgePortConfig := struct {
BridgePortConfigMetaData
*LinuxBridgePortConfig
}{}
ovsBridgePortConfig := struct {
BridgePortConfigMetaData
*OvsBridgePortConfig
}{}
var linuxErr, ovsErr error
if linuxErr = strictDecoder(data).Decode(&linuxBridgePortConfig); linuxErr == nil {
o.BridgePortConfigMetaData = linuxBridgePortConfig.BridgePortConfigMetaData
o.LinuxBridgePortConfig = linuxBridgePortConfig.LinuxBridgePortConfig
} else if ovsErr = strictDecoder(data).Decode(&ovsBridgePortConfig); ovsErr == nil {
o.BridgePortConfigMetaData = ovsBridgePortConfig.BridgePortConfigMetaData
o.OvsBridgePortConfig = ovsBridgePortConfig.OvsBridgePortConfig
} else {
return errors.Join(linuxErr, ovsErr)
}
return nil
}

func (o BridgePortConfig) MarshalJSON() ([]byte, error) {
if o.LinuxBridgePortConfig != nil {
return json.Marshal(struct {
BridgePortConfigMetaData
*LinuxBridgePortConfig
}{
o.BridgePortConfigMetaData,
o.LinuxBridgePortConfig,
})
}
if o.OvsBridgePortConfig != nil {
return json.Marshal(struct {
BridgePortConfigMetaData
*OvsBridgePortConfig
}{
o.BridgePortConfigMetaData,
o.OvsBridgePortConfig,
})
}
return json.Marshal(o.BridgePortConfigMetaData)
}

func (o *OvsBridgeStpOptions) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &o.Enabled); err == nil {
return nil
}
type ovsBridgeStpOptions OvsBridgeStpOptions
stp := ovsBridgeStpOptions{}
if err := json.Unmarshal(data, &stp); err != nil {
return err
}
o.Enabled = stp.Enabled
return nil
}

func (o OvsBridgeStpOptions) MarshalJSON() ([]byte, error) {
type ovsBridgeStpOptions OvsBridgeStpOptions
stp := ovsBridgeStpOptions{}
stp.Enabled = o.Enabled
return json.Marshal(&stp)
}

func (o LldpNeighborTlv) MarshalJSON() ([]byte, error) {
if o.LldpSystemName != nil {
return json.Marshal(o.LldpSystemName)
} else if o.LldpSystemDescription != nil {
return json.Marshal(o.LldpSystemDescription)
} else if o.LldpSystemCapabilities != nil {
return json.Marshal(o.LldpSystemCapabilities)
} else if o.LldpChassisId != nil {
return json.Marshal(o.LldpChassisId)
} else if o.LldpPortId != nil {
return json.Marshal(o.LldpPortId)
} else if o.LldpVlans != nil {
return json.Marshal(o.LldpVlans)
} else if o.LldpMacPhy != nil {
return json.Marshal(o.LldpMacPhy)
} else if o.LldpPpvids != nil {
return json.Marshal(o.LldpPpvids)
} else if o.LldpMgmtAddrs != nil {
return json.Marshal(o.LldpMgmtAddrs)
} else if o.LldpMaxFrameSize != nil {
return json.Marshal(o.LldpMaxFrameSize)
} else {
return nil, fmt.Errorf("unexpected LldpNeighborTlv: %+v", o)
}
}

func (o *LldpNeighborTlv) UnmarshalJSON(data []byte) error {
neighbour := struct {
Type LldpNeighborTlvType
Subtype *LldpOrgSubtype
}{}
if err := json.Unmarshal(data, &neighbour); err != nil {
return fmt.Errorf("failed unmarshaling type and subtype: %v", err)
}
switch neighbour.Type {
case LldpNeighborTlvTypeSystemName:
o.LldpSystemName = &LldpSystemName{}
return strictDecoder(data).Decode(o.LldpSystemName)
case LldpNeighborTlvTypeSystemDescription:
o.LldpSystemDescription = &LldpSystemDescription{}
return strictDecoder(data).Decode(o.LldpSystemDescription)
case LldpNeighborTlvTypeSystemCapabilities:
o.LldpSystemCapabilities = &LldpSystemCapabilities{}
return strictDecoder(data).Decode(o.LldpSystemCapabilities)
case LldpNeighborTlvTypeChassisId:
o.LldpChassisId = &LldpChassisId{}
return strictDecoder(data).Decode(o.LldpChassisId)
case LldpNeighborTlvTypePort:
o.LldpPortId = &LldpPortId{}
return strictDecoder(data).Decode(o.LldpPortId)
case LldpNeighborTlvTypeManagementAddress:
o.LldpMgmtAddrs = &LldpMgmtAddrs{}
return strictDecoder(data).Decode(o.LldpMgmtAddrs)
case LldpNeighborTlvTypeOrganizationSpecific:
if neighbour.Subtype == nil {
return fmt.Errorf("missing lldp neighbour org subtype")
}
switch *neighbour.Subtype {
case LldpOrgSubtypeVlan:
o.LldpVlans = &LldpVlans{}
return strictDecoder(data).Decode(o.LldpVlans)
case LldpOrgSubtypePpvids:
o.LldpPpvids = &LldpPpvids{}
return strictDecoder(data).Decode(o.LldpPpvids)
case LldpOrgSubtypeMacPhyConf:
o.LldpMacPhy = &LldpMacPhy{}
return strictDecoder(data).Decode(o.LldpMacPhy)
case LldpOrgSubtypeMaxFrameSize:
o.LldpMaxFrameSize = &LldpMaxFrameSize{}
return strictDecoder(data).Decode(o.LldpMaxFrameSize)
default:
return fmt.Errorf("unknown lldp neighbour org subtype: %+v", neighbour.Subtype)
}
default:
return fmt.Errorf("unknown lldp neighbour type: %+v", neighbour.Type)
}
}

func strictDecoder(data []byte) *json.Decoder {
decoder := json.NewDecoder(bytes.NewBuffer(data))
decoder.DisallowUnknownFields()
return decoder
}

func (s NetworkState) String() string {
raw, err := json.Marshal(&s)
if err != nil {
return ""
}
return string(raw)
}
21 changes: 21 additions & 0 deletions rust/src/go/api/v2/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module github.com/nmstate/nmstate/rust/src/go/api/v2

go 1.19

require (
github.com/stretchr/testify v1.8.1
k8s.io/apimachinery v0.27.4
sigs.k8s.io/yaml v1.3.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/jstemmer/go-junit-report/v2 v2.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.90.1 // indirect
)

0 comments on commit e6965f2

Please sign in to comment.