Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func main() {
| [RFC5277 NETCONF Event Notifications][RFC5277] | :white_check_mark: supported |
| [RFC5717 Partial Lock Remote Procedure Call (RPC) for NETCONF][RFC5717] | :bulb: planned |
| [RFC8071 NETCONF Call Home and RESTCONF Call Home][RFC8071] | :bulb: planned |
| [RFC6243 With-defaults Capability for NETCONF][RFC6243] | :bulb: planned |
| [RFC6243 With-defaults Capability for NETCONF][RFC6243] | :white_check_mark: supported |
| [RFC4743 Using NETCONF over the Simple Object Access Protocol (SOAP)][RFC4743] | :x: not planned |
| [RFC4744 Using the NETCONF Protocol over the BEEP][RFC4744] | :x: not planned |

Expand Down
4 changes: 2 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
- [ ] Pool/SessionManager for automatic reconnects, retries, etc.
- [ ] Call Home support
- [ ] nccurl command to issue rpc requests from the cli
- [ ] More RFC support
- [~] More RFC support
- [ ] Partial Lock
[ ] with-defaults
- [X] with-defaults
32 changes: 22 additions & 10 deletions rpc/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,20 +62,26 @@ func (u URL) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
//
// [RFC6241 7.1]: https://www.rfc-editor.org/rfc/rfc6241.html#section-7.1
type GetConfig struct {
Source Datastore
Filter Filter
Source Datastore
Filter Filter
WithDefaults WithDefaultsMode
}

func (op GetConfig) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
req := struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:netconf:base:1.0 get-config"`
Source Datastore `xml:"source"`
Filter Filter `xml:"filter,omitempty"`
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:netconf:base:1.0 get-config"`
Source Datastore `xml:"source"`
Filter Filter `xml:"filter,omitempty"`
WithDefaults *withDefaultsElement `xml:",omitempty"`
}{
Source: op.Source,
Filter: op.Filter,
}

if op.WithDefaults != "" {
req.WithDefaults = &withDefaultsElement{Mode: op.WithDefaults}
}

return e.Encode(&req)
}

Expand Down Expand Up @@ -232,20 +238,26 @@ func (rpc EditConfig) Exec(ctx context.Context, session *netconf.Session) error
//
// [RFC6241 7.3] https://www.rfc-editor.org/rfc/rfc6241.html#section-7.3
type CopyConfig struct {
Source any
Target any
Source any
Target any
WithDefaults WithDefaultsMode
}

func (rpc CopyConfig) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
req := struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:netconf:base:1.0 copy-config"`
Source any `xml:"source"`
Target any `xml:"target"`
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:netconf:base:1.0 copy-config"`
Source any `xml:"source"`
Target any `xml:"target"`
WithDefaults *withDefaultsElement `xml:",omitempty"`
}{
Source: rpc.Source,
Target: rpc.Target,
}

if rpc.WithDefaults != "" {
req.WithDefaults = &withDefaultsElement{Mode: rpc.WithDefaults}
}

return e.Encode(&req)
}

Expand Down
13 changes: 10 additions & 3 deletions rpc/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,23 @@ type OkReply struct {
}

type Get struct {
Filter Filter `xml:"filter,omitempty"`
Filter Filter
WithDefaults WithDefaultsMode
}

func (rpc *Get) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
req := struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:netconf:base:1.0 get"`
Filter Filter `xml:"filter,omitempty"`
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:netconf:base:1.0 get"`
Filter Filter `xml:"filter,omitempty"`
WithDefaults *withDefaultsElement `xml:",omitempty"`
}{
Filter: rpc.Filter,
}

if rpc.WithDefaults != "" {
req.WithDefaults = &withDefaultsElement{Mode: rpc.WithDefaults}
}

return e.Encode(&req)
}

Expand Down
30 changes: 30 additions & 0 deletions rpc/with_defaults.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package rpc

import "encoding/xml"

// WithDefaultsMode specifies how default values should be reported
// as defined in RFC 6243.
type WithDefaultsMode string

const (
// DefaultsReportAll returns all data nodes including those set to their
// schema default values.
DefaultsReportAll WithDefaultsMode = "report-all"

// DefaultsReportAllTagged returns all data nodes, with default nodes
// marked with a default="true" attribute.
DefaultsReportAllTagged WithDefaultsMode = "report-all-tagged"

// DefaultsTrim omits data nodes set to their schema default values.
DefaultsTrim WithDefaultsMode = "trim"

// DefaultsExplicit reports only nodes that have been explicitly set
// by the client, plus any state data.
DefaultsExplicit WithDefaultsMode = "explicit"
)

// withDefaultsElement is a helper for marshaling the with-defaults element.
type withDefaultsElement struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults with-defaults"`
Mode WithDefaultsMode `xml:",chardata"`
}
131 changes: 131 additions & 0 deletions rpc/with_defaults_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package rpc

import (
"encoding/xml"
"testing"

"github.com/carlmjohnson/be"
)

func TestGetConfig_WithDefaults_MarshalXML(t *testing.T) {
tests := []struct {
name string
op GetConfig
expected string
}{
{
name: "without with-defaults",
op: GetConfig{
Source: Running,
},
expected: `<get-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><source><running/></source></get-config>`,
},
{
name: "report-all",
op: GetConfig{
Source: Running,
WithDefaults: DefaultsReportAll,
},
expected: `<get-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><source><running/></source><with-defaults xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults">report-all</with-defaults></get-config>`,
},
{
name: "trim",
op: GetConfig{
Source: Running,
WithDefaults: DefaultsTrim,
},
expected: `<get-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><source><running/></source><with-defaults xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults">trim</with-defaults></get-config>`,
},
{
name: "explicit",
op: GetConfig{
Source: Running,
WithDefaults: DefaultsExplicit,
},
expected: `<get-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><source><running/></source><with-defaults xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults">explicit</with-defaults></get-config>`,
},
{
name: "report-all-tagged",
op: GetConfig{
Source: Running,
WithDefaults: DefaultsReportAllTagged,
},
expected: `<get-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><source><running/></source><with-defaults xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults">report-all-tagged</with-defaults></get-config>`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := xml.Marshal(tt.op)
be.NilErr(t, err)
be.Equal(t, tt.expected, string(got))
})
}
}

func TestGet_WithDefaults_MarshalXML(t *testing.T) {
tests := []struct {
name string
op Get
expected string
}{
{
name: "without with-defaults",
op: Get{},
expected: `<get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"></get>`,
},
{
name: "report-all",
op: Get{
WithDefaults: DefaultsReportAll,
},
expected: `<get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><with-defaults xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults">report-all</with-defaults></get>`,
},
{
name: "trim",
op: Get{
WithDefaults: DefaultsTrim,
},
expected: `<get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><with-defaults xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults">trim</with-defaults></get>`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := xml.Marshal(&tt.op)
be.NilErr(t, err)
be.Equal(t, tt.expected, string(got))
})
}
}

func TestCopyConfig_WithDefaults_MarshalXML(t *testing.T) {
tests := []struct {
name string
op CopyConfig
expected string
}{
{
name: "without with-defaults",
op: CopyConfig{
Source: Running,
Target: Startup,
},
expected: `<copy-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><source><running/></source><target><startup/></target></copy-config>`,
},
{
name: "with explicit",
op: CopyConfig{
Source: Running,
Target: Startup,
WithDefaults: DefaultsExplicit,
},
expected: `<copy-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><source><running/></source><target><startup/></target><with-defaults xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults">explicit</with-defaults></copy-config>`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := xml.Marshal(tt.op)
be.NilErr(t, err)
be.Equal(t, tt.expected, string(got))
})
}
}