diff --git a/docs/examples/lldp.yaml b/docs/examples/lldp.yaml index f4887c1b6..078da272a 100644 --- a/docs/examples/lldp.yaml +++ b/docs/examples/lldp.yaml @@ -5,7 +5,7 @@ metadata: spec: capture: ethernets: interfaces.type=="ethernet" - ethernets-up: capture.ethernets | interfaces.state=="up" ethernets-lldp: capture.ethernets-up | interfaces.lldp.enabled:=true + ethernets-up: capture.ethernets | interfaces.state=="up" desiredState: interfaces: "{{ capture.ethernets-lldp.interfaces }}" diff --git a/pkg/nmpolicy/generate.go b/pkg/nmpolicy/generate.go index 105ca169a..04e70dd9f 100644 --- a/pkg/nmpolicy/generate.go +++ b/pkg/nmpolicy/generate.go @@ -18,105 +18,55 @@ limitations under the License. package nmpolicy import ( - "github.com/nmstate/nmpolicy/nmpolicy" - nmpolicytypes "github.com/nmstate/nmpolicy/nmpolicy/types" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "encoding/json" nmstateapi "github.com/nmstate/kubernetes-nmstate/api/shared" + "github.com/nmstate/kubernetes-nmstate/pkg/nmstatectl" ) -type NMPolicyGenerator interface { - GenerateState( - nmpolicySpec nmpolicytypes.PolicySpec, - currentState []byte, - cache nmpolicytypes.CachedState, - ) (nmpolicytypes.GeneratedState, error) -} - -type GenerateStateWithNMPolicy struct{} - -func (GenerateStateWithNMPolicy) GenerateState( - nmpolicySpec nmpolicytypes.PolicySpec, - currentState []byte, - cache nmpolicytypes.CachedState, -) (nmpolicytypes.GeneratedState, error) { - return nmpolicy.GenerateState(nmpolicySpec, currentState, cache) -} - -// The method generates the state using the default NMPolicyGenerator -func GenerateState(desiredState nmstateapi.State, - policySpec nmstateapi.NodeNetworkConfigurationPolicySpec, - currentState nmstateapi.State, - cachedState map[string]nmstateapi.NodeNetworkConfigurationEnactmentCapturedState) ( - map[string]nmstateapi.NodeNetworkConfigurationEnactmentCapturedState, /* resolved captures */ - nmstateapi.State, /* updated desired state */ - error) { - return GenerateStateWithStateGenerator(GenerateStateWithNMPolicy{}, desiredState, policySpec, currentState, cachedState) -} - -// The method generates the state using NMPolicyGenerator.GenerateState and then converts the returned value to the match the enactment api -func GenerateStateWithStateGenerator(stateGenerator NMPolicyGenerator, +func GenerateState( desiredState nmstateapi.State, policySpec nmstateapi.NodeNetworkConfigurationPolicySpec, currentState nmstateapi.State, cachedState map[string]nmstateapi.NodeNetworkConfigurationEnactmentCapturedState) ( - map[string]nmstateapi.NodeNetworkConfigurationEnactmentCapturedState, - nmstateapi.State, error) { - nmpolicySpec := nmpolicytypes.PolicySpec{ + map[string]nmstateapi.NodeNetworkConfigurationEnactmentCapturedState, nmstateapi.State, error) { + if len(desiredState.Raw) == 0 || len(currentState.Raw) == 0 { + return map[string]nmstateapi.NodeNetworkConfigurationEnactmentCapturedState{}, + nmstateapi.State{}, nil + } + nmstatePolicy := struct { + Capture map[string]string `json:"capture,omitempty"` + DesiredState nmstateapi.State `json:"desiredState,omitempty"` + }{ Capture: policySpec.Capture, - DesiredState: []byte(desiredState.Raw), + DesiredState: policySpec.DesiredState, } - nmpolicyGeneratedState, err := stateGenerator.GenerateState( - nmpolicySpec, - currentState.Raw, - convertCachedStateFromEnactment(cachedState), - ) + + nmstatePolicyRaw, err := json.Marshal(nmstatePolicy) if err != nil { return map[string]nmstateapi.NodeNetworkConfigurationEnactmentCapturedState{}, nmstateapi.State{}, err } - - capturedStates, desiredState := convertGeneratedStateToEnactmentConfig(nmpolicyGeneratedState) - return capturedStates, desiredState, nil -} - -func convertGeneratedStateToEnactmentConfig(nmpolicyGeneratedState nmpolicytypes.GeneratedState) ( - map[string]nmstateapi.NodeNetworkConfigurationEnactmentCapturedState, nmstateapi.State) { - capturedStates := map[string]nmstateapi.NodeNetworkConfigurationEnactmentCapturedState{} - - for captureKey, capturedState := range nmpolicyGeneratedState.Cache.Capture { - capturedState := nmstateapi.NodeNetworkConfigurationEnactmentCapturedState{ - State: nmstateapi.State{ - Raw: []byte(capturedState.State), - }, - MetaInfo: convertMetaInfoToEnactment(capturedState.MetaInfo), + cachedStateRaw := []byte{} + if len(cachedState) > 0 { + cachedStateRaw, err = json.Marshal(cachedState) + if err != nil { + return map[string]nmstateapi.NodeNetworkConfigurationEnactmentCapturedState{}, + nmstateapi.State{}, err } - capturedStates[captureKey] = capturedState } - return capturedStates, nmstateapi.State{Raw: []byte(nmpolicyGeneratedState.DesiredState)} -} -func convertCachedStateFromEnactment( - enactmentCachedState map[string]nmstateapi.NodeNetworkConfigurationEnactmentCapturedState, -) nmpolicytypes.CachedState { - cachedState := nmpolicytypes.CachedState{Capture: make(map[string]nmpolicytypes.CaptureState)} - for captureKey, capturedState := range enactmentCachedState { - capturedState := nmpolicytypes.CaptureState{ - State: nmpolicytypes.NMState(capturedState.State.Raw), - MetaInfo: nmpolicytypes.MetaInfo{ - Version: capturedState.MetaInfo.Version, - TimeStamp: capturedState.MetaInfo.TimeStamp.Time, - }, - } - cachedState.Capture[captureKey] = capturedState + output, capturedStateRaw, err := nmstatectl.Policy(nmstatePolicyRaw, []byte(currentState.Raw), cachedStateRaw) + if err != nil { + return map[string]nmstateapi.NodeNetworkConfigurationEnactmentCapturedState{}, + nmstateapi.State{}, err } - return cachedState -} -func convertMetaInfoToEnactment(metaInfo nmpolicytypes.MetaInfo) nmstateapi.NodeNetworkConfigurationEnactmentMetaInfo { - return nmstateapi.NodeNetworkConfigurationEnactmentMetaInfo{ - Version: metaInfo.Version, - TimeStamp: metav1.NewTime(metaInfo.TimeStamp), + capturedState := map[string]nmstateapi.NodeNetworkConfigurationEnactmentCapturedState{} + if err := json.Unmarshal(capturedStateRaw, &capturedState); err != nil { + return map[string]nmstateapi.NodeNetworkConfigurationEnactmentCapturedState{}, + nmstateapi.State{}, err } + + return capturedState, nmstateapi.State{Raw: output}, nil } diff --git a/pkg/nmpolicy/generate_test.go b/pkg/nmpolicy/generate_test.go deleted file mode 100644 index 52404e1bd..000000000 --- a/pkg/nmpolicy/generate_test.go +++ /dev/null @@ -1,111 +0,0 @@ -/* -Copyright The Kubernetes 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. -*/ - -package nmpolicy - -import ( - "fmt" - "time" - - nmstateapi "github.com/nmstate/kubernetes-nmstate/api/shared" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - nmpolicytypes "github.com/nmstate/nmpolicy/nmpolicy/types" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("NMPolicy GenerateState", func() { - When("fails", func() { - It("Should return an error", func() { - capturedState, desiredState, err := GenerateStateWithStateGenerator( - nmpolicyStub{shouldFail: true}, - nmstateapi.State{}, - nmstateapi.NodeNetworkConfigurationPolicySpec{}, - nmstateapi.State{}, - map[string]nmstateapi.NodeNetworkConfigurationEnactmentCapturedState{}, - ) - Expect(err).To(HaveOccurred()) - Expect(capturedState).To(Equal(map[string]nmstateapi.NodeNetworkConfigurationEnactmentCapturedState{})) - Expect(desiredState).To(Equal(nmstateapi.State{})) - }) - - }) - - When("succeeds", func() { - const desiredStateYaml = `desire state yaml` - const captureYaml1 = `default-gw expression` - const captureYaml2 = `base-iface expression` - const metaVersion = "5" - metaTime := time.Now() - - nmpolicyMetaInfo := nmpolicytypes.MetaInfo{ - Version: metaVersion, - TimeStamp: metaTime, - } - - generatedState := nmpolicytypes.GeneratedState{ - DesiredState: []byte(desiredStateYaml), - Cache: nmpolicytypes.CachedState{ - Capture: map[string]nmpolicytypes.CaptureState{ - "default-gw": {State: []byte(captureYaml1), MetaInfo: nmpolicyMetaInfo}, - "base-iface": {State: []byte(captureYaml2)}, - }, - }, - } - - capturedStates, desiredState, err := GenerateStateWithStateGenerator( - nmpolicyStub{output: generatedState}, - nmstateapi.State{}, - nmstateapi.NodeNetworkConfigurationPolicySpec{}, - nmstateapi.State{}, - map[string]nmstateapi.NodeNetworkConfigurationEnactmentCapturedState{}, - ) - - Expect(err).NotTo(HaveOccurred()) - - expectedMetaInfo := nmstateapi.NodeNetworkConfigurationEnactmentMetaInfo{ - Version: metaVersion, - TimeStamp: metav1.NewTime(metaTime), - } - - expectedcCaptureCache := map[string]nmstateapi.NodeNetworkConfigurationEnactmentCapturedState{ - "default-gw": {State: nmstateapi.State{Raw: []byte(captureYaml1)}, MetaInfo: expectedMetaInfo}, - "base-iface": {State: nmstateapi.State{Raw: []byte(captureYaml2)}}, - } - - Expect(capturedStates).To(Equal(expectedcCaptureCache)) - Expect(desiredState).To(Equal(nmstateapi.State{Raw: []byte(desiredStateYaml)})) - }) -}) - -type nmpolicyStub struct { - shouldFail bool - output nmpolicytypes.GeneratedState -} - -func (n nmpolicyStub) GenerateState( - nmpolicySpec nmpolicytypes.PolicySpec, - currentState []byte, - cache nmpolicytypes.CachedState, -) (nmpolicytypes.GeneratedState, error) { - if n.shouldFail { - return nmpolicytypes.GeneratedState{}, fmt.Errorf("GenerateStateFailed") - } - return n.output, nil -} diff --git a/pkg/nmstatectl/nmstatectl.go b/pkg/nmstatectl/nmstatectl.go index c00082000..cfeb50289 100644 --- a/pkg/nmstatectl/nmstatectl.go +++ b/pkg/nmstatectl/nmstatectl.go @@ -21,6 +21,7 @@ import ( "bytes" "fmt" "io" + "os" "os/exec" "strconv" "strings" @@ -141,3 +142,63 @@ func Statistic(desiredState nmstate.State) (*Stats, error) { } return NewStats(stats.Features), nil } + +func Policy(policy, currentState, capturedState []byte) (desiredState, generatedCapturedState []byte, err error) { + policyFile, err := generateFileWithContent("policy", policy) + if err != nil { + return nil, nil, err + } + defer func() { + os.Remove(policyFile) + }() + + args := []string{"policy"} + if len(currentState) > 0 { + var currentStateFile string + currentStateFile, err = generateFileWithContent("currentState", currentState) + if err != nil { + return nil, nil, err + } + defer func() { + os.Remove(currentStateFile) + }() + args = append(args, "--current", currentStateFile) + } + + args = append(args, "--json") + + capturedStateFile, err := generateFileWithContent("capturedState", capturedState) + if err != nil { + return nil, nil, err + } + defer func() { + os.Remove(capturedStateFile) + }() + args = append(args, "--output-captured", capturedStateFile) + if len(capturedState) > 0 { + args = append(args, "--captured", capturedStateFile) + } + + args = append(args, policyFile) + out, err := nmstatectl(args) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed calling nmstatectl rollback") + } + capturedState, err = os.ReadFile(capturedStateFile) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed failed reading captured state") + } + return []byte(out), capturedState, nil +} + +func generateFileWithContent(name string, content []byte) (string, error) { + file, err := os.CreateTemp("/tmp", name) + if err != nil { + return "", err + } + defer file.Close() + if _, err := file.Write(content); err != nil { + return "", err + } + return file.Name(), nil +} diff --git a/test/e2e/handler/nnce_conditions_test.go b/test/e2e/handler/nnce_conditions_test.go index 14e55bb3a..d14cbf6cc 100644 --- a/test/e2e/handler/nnce_conditions_test.go +++ b/test/e2e/handler/nnce_conditions_test.go @@ -18,7 +18,6 @@ limitations under the License. package handler import ( - "fmt" "sync" "time" @@ -30,14 +29,6 @@ import ( policyconditions "github.com/nmstate/kubernetes-nmstate/test/e2e/policy" ) -func invalidConfig(bridgeName string) nmstate.State { - return nmstate.NewState(fmt.Sprintf(`interfaces: - - name: %s - type: linux-bridge - state: invalid_state -`, bridgeName)) -} - var _ = Describe("EnactmentCondition", func() { Context("when applying valid config", func() { AfterEach(func() { @@ -76,7 +67,11 @@ var _ = Describe("EnactmentCondition", func() { Context("when applying invalid configuration", func() { BeforeEach(func() { - updateDesiredState(invalidConfig(bridge1)) + updateDesiredState(nmstate.NewState(`interfaces: + - name: bad1 + type: ethernet + state: up +`)) }) AfterEach(func() {