Skip to content

Commit

Permalink
drivenets: DNOS CLI support for ondatra framework
Browse files Browse the repository at this point in the history
Signed-off-by: Marius Ionescu <[email protected]>
  • Loading branch information
mionescu-dn committed Nov 25, 2024
1 parent fc41e32 commit 09240f9
Show file tree
Hide file tree
Showing 20 changed files with 48,384 additions and 47,357 deletions.
5 changes: 2 additions & 3 deletions binding/grpcutil/testservice/gen/testservice.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion binding/grpcutil/testservice/gen/testservice_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions config/vendor.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,12 @@ func (c *VendorConfig) WithJuniperText(text string) *VendorConfig {
return c
}

// WithDrivenetsText sets the config to be pushed if the DUT vendor is Drivenets.
func (c *VendorConfig) WithDrivenetsText(text string) *VendorConfig {
c.perVendor[opb.Device_DRIVENETS] = configText(text)
return c
}

// WithFile sets the config to be pushed regardless of the DUT vendor.
// This should only be used when the DUT vendor was already taken into account
// in the generation of the config and only when no per-vendor configs are set.
Expand Down Expand Up @@ -173,6 +179,12 @@ func (c *VendorConfig) WithJuniperFile(path string) *VendorConfig {
return c
}

// WithDrivenetsFile sets the config to be pushed if the DUT vendor is Drivenets.
func (c *VendorConfig) WithDrivenetsFile(path string) *VendorConfig {
c.perVendor[opb.Device_DRIVENETS] = configFile(path)
return c
}

// WithVarValue replaces each occurrence of {{ var "key" }} in the pushed config
// with the specified value.
func (c *VendorConfig) WithVarValue(key, value string) *VendorConfig {
Expand Down
136 changes: 136 additions & 0 deletions dnbind/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# Drivenets Binding

Drivenets Binding is an implementation of the Ondatra binding interface with support
limited to [Config](https://pkg.go.dev/github.com/openconfig/ondatra/config) interface.


## Flags

Drivenets Binding integration tests requires a `--config` flag be passed that specifies device information.
Running Drivenets Binding tests requires passing device credential by using `--node_cres` flag.


### Device Credentials

An example of credentials flags:

```
--node_creds=hostname/user/pass
```

An example of yaml configuration file where `id` needs to match testbed `id`:

```
nodes:
- id: testbed_id
hostname: foo
credentials:
username: name
password: pass
```


## Running the Integration Test

To execute the test, you must update config.yaml with your Drivenets device details
and pass both the testbed and config files as flags to the test:

```
go test github.com/openconfig/ondatra/dnbind/integration --testbed=testbed.textproto --config=config.yaml
```

This repo includes an
[example integration test](integration/integration_test.go) that uses the Drivenets
binding, a [testbed file](integration/testbed.textproto) for that test, and a
[mock configuration file](integration/config.yaml) that is matched by the
testbed.

## Session idle timeout

CLI might disconnect if idle for prolonged period of time.
One can disable session timeout be configuring: ```system login session-timeout 0```.
This does not affect active CLI connection, so for intendeed behaviour one must reconnect.

Sample usage:
```golang
dut := ondatra.DUT(t, "dut")
// disable session timeout
dut.Config().New().
WithDrivenetsText(
`system login session-timeout 0
`).
Append(t)
dut := ondatra.DUT(t, "dut")
/* your code goes here with disabled session timeout */
```


## Interactive Commands

Operational commands requiring confirmation or user input of any kind are not supported by this API.

### Disable Show commands pagination (Recommended)

By running ```set cli-terminal-length 0``` as an initial step or apending ``` | no-more ``` to all show commands.
Paginated output leads to command getting stuck waiting for prompt.

### List of unsupported commands

- ‘run monitor ...’
- Any command followed by ```‘| monitor interval’```

*Commands that exit from CLI:*
- ‘exit’
- ‘quit’

*Interactive commands that initiate outbound connection and require user input:*
- ‘run ssh ...’
- ‘run ipmi ...’
- ‘run start shell ...’

*Commands that require confirmation:*
- GI:
- ‘request system delete’
- ‘request system deploy’
- ‘request system install’
- ‘request system revert-stack’
- ‘request system target-stack ...’
- ‘request system tech-support ...’ ***can bypass using ‘force’ keyword***

- DNOS:
- ‘load override golden-config'
- ‘request system tech-support ...’ ***can bypass using ‘force’ keyword***
- ‘request system restart factory-default'
- ‘request file copy’
- ‘request file delete’
- ‘request interface management <xxx> access-list'
- ‘request system delete’
- ‘request system generate golden-config'
- ‘request system ncc switchover’
- ‘request system container restart’
- ‘request system process restart’
- ‘request system process stop’
- ‘request system restart <xxx>’
- ‘request system revert-stack'
- ‘request system target-stack'

- Interactive configuration commands:
- ‘system profile’

- Configuration commands that take plain-text password value.
Using plain-text option is interactive.
If you want to configure password value, pass the encrypted password value instead
- ‘system aaa-server radius server password’
- ‘system login ipmi user password’
- ‘system login ncm user password’
- ‘system login user password’
- ‘system ntp authentication key-id'
- ‘system snmp user authentication password’
- ‘protocols mpls traffic-engineering pcep authentication enabled password’
- ‘protocols ldp authentication md5 password’
- ‘protocols ldp neighbor <> authentication md5 password’
- ‘protocols ospf area <> interface <> authentication-key md5 key-id <> password’
- ‘protocols ospfv3 area <> authentication ipsec spi <> md5 password’
- ‘protocols ospfv3 area <> interface <> authentication ‘
- ‘system aaa-server tacacs server priority <> address <> password’
- ‘protocols bgp <> neighbor <> authentication md5 password’
72 changes: 72 additions & 0 deletions dnbind/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2021 Google LLC
//
// 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
//
// https://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 dnbind

import (
"fmt"
"os"

"github.com/openconfig/ondatra/dnbind/creds"
"gopkg.in/yaml.v2"
)

type Node struct {
Id string `yaml:"id"`
Hostname string `yaml:"hostname"`
Vendor string `yaml:"vendor,omitempty"`
HardwareModel string `yaml:"model,omitempty"`
SoftwareVersion string `yaml:"version,omitempty"`
Credentials creds.UserPass `yaml:"credentials,omitempty"`
}

// Config contains parameters to configure the Drivenets binding.
type Config struct {
Credentials *creds.Credentials
Nodes []Node `yaml:"nodes"`
}

func (c *Config) String() string {
return fmt.Sprintf("%+v", *c)
}

func (c *Config) Lookup(id string) *Node {
for _, node := range c.Nodes {
if node.Id == id {
return &node
}
}
return nil
}

// ParseCredFile parses a yaml file containing a serialized Config.
func ParseConfigFile(credFile string) (*Config, error) {
data, err := os.ReadFile(credFile)
if err != nil {
return nil, fmt.Errorf("error reading config file: %w", err)
}
c := &Config{
Credentials: &creds.Credentials{
Node: make(map[string]*creds.UserPass),
},
}
if err := yaml.Unmarshal(data, c); err != nil {
return nil, fmt.Errorf("error unmarshalling config YAML: %w", err)
}
for _, node := range c.Nodes {
c.Credentials.Node[node.Hostname] = &node.Credentials
}

return c, nil
}
106 changes: 106 additions & 0 deletions dnbind/creds/creds.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2023 Google LLC
//
// 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
//
// https://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 creds contains logic for DNOS credentials.
package creds

import (
"flag"
"fmt"
"strings"
)

// UserPass is a username/password combination.
type UserPass struct {
Username string `yaml:"username"`
Password string `yaml:"password"`
}

func (up *UserPass) String() string {
return fmt.Sprintf("%s/%s", up.Username, up.Password)
}

type Credentials struct {
Node map[string]*UserPass
}

// Lookup returns the username/password to use for the given node name and vendor.
// Returns nil if no such combination exists. This method is nil-tolerant.
func (c *Credentials) Lookup(node string) *UserPass {
if c != nil {
if up, ok := c.Node[node]; ok {
return up
}
}
return nil
}

func (c *Credentials) String() string {
return fmt.Sprintf("%+v", *c)
}

// DefineFlags defines flags for allowing user-specified credentials.
func DefineFlags() *Flags {
flags := new(Flags)
multiStringVar(&flags.nodeCreds, "node_creds",
"Repeated node-specific credentials in the form 'nodeName/username/password', e.g., 'n1/admin/hunter2'")
return flags
}

func multiStringVar(v *[]string, name, usage string) {
flag.Var((*multiStringVal)(v), name, usage)
}

type multiStringVal []string

func (v *multiStringVal) Get() any {
return []string(*v)
}

func (v *multiStringVal) Set(s string) error {
*v = append(*v, s)
return nil
}

func (*multiStringVal) TypeDescription() string {
return "repeated string"
}

func (v *multiStringVal) String() string {
if len(*v) == 0 {
return ""
}
return fmt.Sprint(*v)
}

// Flags are a collection of credentials flags.
type Flags struct {
nodeCreds []string
}

// Parse parses the flags into Credentials.
func (f *Flags) Parse() (*Credentials, error) {
c := new(Credentials)
if len(f.nodeCreds) > 0 {
c.Node = make(map[string]*UserPass, len(f.nodeCreds))
for _, nc := range f.nodeCreds {
parts := strings.SplitN(nc, "/", 3)
if len(parts) != 3 {
return nil, fmt.Errorf("invalid node credentials: %q", nc)
}
c.Node[parts[0]] = &UserPass{Username: parts[1], Password: parts[2]}
}
}
return c, nil
}
Loading

0 comments on commit 09240f9

Please sign in to comment.