Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drivenets #115

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
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