コード例 #1
0
ファイル: rip_test.go プロジェクト: udhos/nexthop
func setup_diff() (*ripTestApp, *ripTestClient) {
	app := &ripTestApp{
		cmdRoot:           &command.CmdNode{MinLevel: command.EXEC},
		confRootCandidate: &command.ConfNode{},
		confRootActive:    &command.ConfNode{},
	}

	hardware := fwd.NewDataplaneBogus()

	listInterfaces := func() ([]string, []string) {
		ifaces, vrfs, err := hardware.Interfaces()
		if err != nil {
			log.Printf("Example_diff: hardware.Interfaces(): error: %v", err)
		}
		return ifaces, vrfs
	}
	listCommitId := func() []string {
		return []string{"BOGUS:rip.Example_diff:listCommitId"}
	}
	command.LoadKeywordTable(listInterfaces, listCommitId)

	root := app.cmdRoot
	cmdNone := command.CMD_NONE
	cmdConf := command.CMD_CONF

	command.CmdInstall(root, cmdNone, "commit", command.CONF, command.HelperCommit, nil, "Apply current candidate configuration")
	command.CmdInstall(root, cmdNone, "show configuration", command.EXEC, command.HelperShowConf, nil, "Show candidate configuration")
	command.CmdInstall(root, cmdNone, "show configuration compare", command.EXEC, command.HelperShowCompare, nil, "Show differences between active and candidate configurations")
	command.CmdInstall(root, cmdNone, "no {ANY}", command.CONF, command.HelperNo, nil, "Remove a configuration item")

	command.CmdInstall(root, cmdConf, "username {USERNAME} password (PASSWORD)", command.EXEC, cmdUsername, command.ApplyBogus, "User clear-text password")
	command.CmdInstall(root, cmdConf, "hostname (HOSTNAME)", command.CONF, command.HelperHostname, command.ApplyBogus, "Hostname")
	command.CmdInstall(root, cmdNone, "show version", command.EXEC, cmdVersion, nil, "Show version")
	command.CmdInstall(root, cmdConf, "router rip", command.CONF, cmdRip, applyRip, "Enable RIP protocol")
	command.CmdInstall(root, cmdConf, "router rip network {NETWORK}", command.CONF, cmdRipNetwork, applyRipNet, "Insert network into RIP protocol")
	command.CmdInstall(root, cmdConf, "router rip network {NETWORK} cost (RIPMETRIC)", command.CONF, cmdRipNetCost, applyRipNetCost, "RIP network metric")
	command.CmdInstall(root, cmdConf, "router rip vrf {VRFNAME} network {NETWORK}", command.CONF, cmdRipNetwork, applyRipNet, "Insert network into RIP protocol")
	command.CmdInstall(root, cmdConf, "router rip vrf {VRFNAME} network {NETWORK} cost (RIPMETRIC)", command.CONF, cmdRipNetCost, applyRipNetCost, "RIP network metric")

	outputSinkFunc := func(m string) {
	}
	c := NewRipTestClient(outputSinkFunc)

	return app, c
}
コード例 #2
0
ファイル: bgp_test.go プロジェクト: udhos/nexthop
func setup_diff() (*bgpTestApp, *bgpTestClient) {
	app := &bgpTestApp{
		cmdRoot:           &command.CmdNode{MinLevel: command.EXEC},
		confRootCandidate: &command.ConfNode{},
		confRootActive:    &command.ConfNode{},
	}

	hardware := fwd.NewDataplaneBogus()

	listInterfaces := func() ([]string, []string) {
		ifaces, vrfs, err := hardware.Interfaces()
		if err != nil {
			log.Printf("Example_diff: hardware.Interfaces(): error: %v", err)
		}
		return ifaces, vrfs
	}
	listCommitId := func() []string {
		return []string{"BOGUS:bgp.Example_diff:listCommitId"}
	}
	command.LoadKeywordTable(listInterfaces, listCommitId)

	root := app.cmdRoot
	cmdNone := command.CMD_NONE
	cmdConf := command.CMD_CONF

	command.CmdInstall(root, cmdNone, "commit", command.CONF, command.HelperCommit, nil, "Apply current candidate configuration")
	command.CmdInstall(root, cmdNone, "show configuration", command.EXEC, command.HelperShowConf, nil, "Show candidate configuration")
	command.CmdInstall(root, cmdNone, "show configuration compare", command.EXEC, command.HelperShowCompare, nil, "Show differences between active and candidate configurations")
	command.CmdInstall(root, cmdNone, "no {ANY}", command.CONF, command.HelperNo, nil, "Remove a configuration item")

	command.CmdInstall(root, cmdConf, "hostname (HOSTNAME)", command.CONF, command.HelperHostname, command.ApplyBogus, "Hostname")
	command.CmdInstall(root, cmdNone, "show version", command.EXEC, cmdVersion, nil, "Show version")
	command.CmdInstall(root, cmdConf, "router bgp {ASN} neighbor {IPADDR} description {ANY}", command.CONF, cmdNeighDesc, command.ApplyBogus, "BGP neighbor description")
	command.CmdInstall(root, cmdConf, "router bgp {ASN} neighbor {IPADDR} remote-as (ASN)", command.CONF, cmdNeighAsn, applyNeighAsn, "BGP neighbor ASN")

	outputSinkFunc := func(m string) {
	}
	c := NewBgpTestClient(outputSinkFunc)

	return app, c
}
コード例 #3
0
ファイル: rip.go プロジェクト: udhos/nexthop
func main() {

	daemonName := "rip"

	log.Printf("%s daemon starting", daemonName)

	rip := &Rip{
		cmdRoot:           &command.CmdNode{Path: "", MinLevel: command.EXEC, Handler: nil},
		confRootCandidate: &command.ConfNode{},
		confRootActive:    &command.ConfNode{},
		daemonName:        daemonName,
		hardware:          fwd.NewDataplaneBogus(),
	}

	var dataplaneName string
	configPrefix := command.ConfigPathRoot + "/" + daemonName + ".conf."
	flag.StringVar(&rip.configPathPrefix, "configPathPrefix", configPrefix, "configuration path prefix")
	flag.IntVar(&rip.maxConfigFiles, "maxConfigFiles", command.DefaultMaxConfigFiles, "limit number of configuration files (negative value means unlimited)")
	flag.StringVar(&dataplaneName, "dataplane", "native", "select forwarding engine")
	flag.Parse()

	rip.hardware = fwd.NewDataplane(dataplaneName)

	listInterfaces := func() ([]string, []string) {
		ifaces, vrfs, err := rip.hardware.Interfaces()
		if err != nil {
			log.Printf("%s main: Interfaces(): error: %v", rip.daemonName, err)
		}
		return ifaces, vrfs
	}
	listCommitId := func() []string {
		_, matches, err := command.ListConfig(rip.ConfigPathPrefix(), true)
		if err != nil {
			log.Printf("%s main: error listing commit id's: %v", rip.daemonName, err)
		}
		var idList []string
		for _, m := range matches {
			id, err1 := command.ExtractCommitIdFromFilename(m)
			if err1 != nil {
				log.Printf("%s main: error extracting commit id's: %v", rip.daemonName, err1)
				continue
			}
			idList = append(idList, strconv.Itoa(id))
		}
		return idList
	}
	command.LoadKeywordTable(listInterfaces, listCommitId)

	installCommands(rip.CmdRoot())

	loadConf(rip)

	cliServer := cli.NewServer()

	go cli.ListenTelnet(":2002", cliServer)

	tick := time.Duration(10)
	ticker := time.NewTicker(time.Second * tick)

	for {
		select {
		case <-ticker.C:
			log.Printf("%s main: %ds tick", rip.daemonName, tick)
		case comm := <-cliServer.CommandChannel:
			log.Printf("%s main: command: isLine=%v len=%d [%s]", rip.daemonName, comm.IsLine, len(comm.Cmd), comm.Cmd)
			cli.Execute(rip, comm.Cmd, comm.IsLine, !comm.HideFromHistory, comm.Client)
		case c := <-cliServer.InputClosed:
			// inputLoop hit closed connection. it's finished.
			// we should discard pending output (if any).
			log.Printf("%s main: inputLoop hit closed connection", rip.daemonName)
			c.DiscardOutputQueue()
		}
	}
}
コード例 #4
0
ファイル: cli_test.go プロジェクト: udhos/nexthop
func TestConf(t *testing.T) {

	app := &testApp{
		cmdRoot:           &command.CmdNode{MinLevel: command.EXEC},
		confRootCandidate: &command.ConfNode{},
		confRootActive:    &command.ConfNode{},
	}

	hardware := fwd.NewDataplaneBogus()

	listInterfaces := func() ([]string, []string) {
		ifaces, vrfs, err := hardware.Interfaces()
		if err != nil {
			t.Errorf("hardware.Interfaces(): error: %v", err)
		}
		return ifaces, vrfs
	}
	listCommitId := func() []string {
		return []string{"BOGUS:TestConf:listCommitId"}
	}
	command.LoadKeywordTable(listInterfaces, listCommitId)

	root := app.cmdRoot
	cmdNone := command.CMD_NONE
	cmdConf := command.CMD_CONF

	cmdBogus := func(ctx command.ConfContext, node *command.CmdNode, line string, c command.CmdClient) {
	}

	command.CmdInstall(root, cmdConf, "interface {IFNAME} description {ANY}", command.CONF, command.HelperIfaceDescr, command.ApplyBogus, "Set interface description")
	command.CmdInstall(root, cmdConf, "interface {IFNAME} ipv4 address {IFADDR}", command.CONF, command.HelperIfaceAddr, command.ApplyBogus, "Assign IPv4 address to interface")
	command.CmdInstall(root, cmdConf, "interface {IFNAME} ipv6 address {IFADDR6}", command.CONF, cmdBogus, command.ApplyBogus, "Assign IPv6 address to interface")
	command.CmdInstall(root, cmdConf, "interface {IFNAME} shutdown", command.CONF, cmdBogus, command.ApplyBogus, "Disable interface")
	command.CmdInstall(root, cmdConf, "ip routing", command.CONF, cmdBogus, command.ApplyBogus, "Enable IP routing")
	command.CmdInstall(root, cmdConf, "hostname (HOSTNAME)", command.CONF, command.HelperHostname, command.ApplyBogus, "Assign hostname")
	command.CmdInstall(root, cmdNone, "no {ANY}", command.CONF, command.HelperNo, nil, "Remove a configuration item")

	c := &testClient{outputChannel: make(chan string)}
	close(c.outputChannel) // closed channel will break writers

	if err := command.Dispatch(app, "", c, command.CONF, false); err != nil {
		t.Errorf("empty command rejected: %v", err)
	}

	if err := command.Dispatch(app, "      ", c, command.CONF, false); err != nil {
		t.Errorf("blank command rejected: %v", err)
	}

	if err := command.Dispatch(app, "  !xxx    ", c, command.CONF, false); err != nil {
		t.Errorf("comment command ! rejected: %v", err)
	}

	if err := command.Dispatch(app, "  #xxx    ", c, command.CONF, false); err != nil {
		t.Errorf("comment command # rejected: %v", err)
	}

	if err := command.Dispatch(app, "xxxxxx", c, command.CONF, false); err == nil {
		t.Errorf("bad command accepted")
	}

	command.Dispatch(app, "hostname nexthop-router", c, command.CONF, false)
	if host := getHostname(app.ConfRootCandidate()); host != "nexthop-router" {
		t.Errorf("bad hostname: %s", host)
	}

	command.Dispatch(app, "int eth0 desc  aa  bb   ccc", c, command.CONF, false)
	node, err := app.confRootCandidate.Get("interface eth0 description")
	if err != nil || node == nil {
		t.Errorf("bad description: %v", err)
		return
	}
	if node.Path != "interface eth0 description" {
		t.Errorf("bad description path: [%s]", node.Path)
	}
	if len(node.Children) != 1 {
		t.Errorf("bad description value count: %d", len(node.Children))
	}
	if v := command.DescriptionDecode(command.LastToken(node.Children[0].Path)); v != " aa  bb   ccc" {
		t.Errorf("bad description value: [%s]", v)
	}

	command.Dispatch(app, "no int eth0 desc xxxxxxx", c, command.CONF, false)
	node, err = app.confRootCandidate.Get("interface eth0 description")
	if node != nil || err == nil {
		t.Errorf("eth0 description should not be present: node=[%v] error=[%v]", node, err)
	}

	command.Dispatch(app, "int eth1 desc ddd   eee   fff ", c, command.CONF, false)
	node, err = app.confRootCandidate.Get("interface eth1 description")
	if err != nil {
		t.Errorf("bad description: %v", err)
	}
	if node.Path != "interface eth1 description" {
		t.Errorf("bad description path: [%s]", node.Path)
	}
	if len(node.Children) != 1 {
		t.Errorf("bad description value count: %d", len(node.Children))
	}
	if v := command.DescriptionDecode(command.LastToken(node.Children[0].Path)); v != "ddd   eee   fff " {
		t.Errorf("bad description value: [%s]", v)
	}

	command.Dispatch(app, "no int eth1 desc", c, command.CONF, false)
	node, err = app.confRootCandidate.Get("interface eth1 description")
	if node != nil || err == nil {
		t.Errorf("eth1 description should not be present: node=[%v] error=[%v]", node, err)
	}

	command.Dispatch(app, "int eth2 ipv4 addr 1.1.1.1/1", c, command.CONF, false)
	node, err = app.confRootCandidate.Get("interface eth2 ipv4 address")
	if err != nil {
		t.Errorf("bad eth2 address 1: %v", err)
		return
	}
	if len(node.Children) != 1 {
		t.Errorf("wrong number of eth2 addresses (expected=1): %d", len(node.Children))
	}
	command.Dispatch(app, "int eth2 ipv4 addr 2.2.2.2/2", c, command.CONF, false)
	if err != nil {
		t.Errorf("bad eth2 address 2: %v", err)
		return
	}
	if len(node.Children) != 2 {
		t.Errorf("wrong number of eth2 addresses (expected=2): %d", len(node.Children))
	}
	command.Dispatch(app, "int eth2 ipv4 addr 3.3.3.3/3", c, command.CONF, false)
	node, err = app.confRootCandidate.Get("interface eth2 ipv4 address")
	if err != nil {
		t.Errorf("bad eth2 address 3: %v", err)
		return
	}
	if len(node.Children) != 3 {
		t.Errorf("wrong number of eth2 addresses (expected=3): %d", len(node.Children))
	}
	command.Dispatch(app, "no int eth2 ipv4 addr 3.3.3.3/3", c, command.CONF, false)
	if len(node.Children) != 2 {
		t.Errorf("wrong number of eth2 addresses (expected=2): %d", len(node.Children))
	}
	command.Dispatch(app, "no int eth2 ipv4 addr", c, command.CONF, false)
	node, err = app.confRootCandidate.Get("interface eth2 ipv4 address")
	if node != nil || err == nil {
		t.Errorf("eth2 should not have address: node=[%v] error=[%v]", node, err)
	}

	command.Dispatch(app, "int eth3 ipv4 addr 1.1.1.1/1", c, command.CONF, false)
	command.Dispatch(app, "int eth3 ipv4 addr 2.2.2.2/2", c, command.CONF, false)
	command.Dispatch(app, "int eth3 ipv4 addr 3.3.3.3/3", c, command.CONF, false)
	command.Dispatch(app, "int eth3 ipv4 addr 4.4.4.4/4", c, command.CONF, false)
	node, err = app.confRootCandidate.Get("interface eth3 ipv4 address")
	if err != nil {
		t.Errorf("bad eth3 address: %v", err)
		return
	}
	if len(node.Children) != 4 {
		t.Errorf("wrong number of eth3 addresses (expected=4): %d", len(node.Children))
	}
	node, err = app.confRootCandidate.Get("interface eth3 ipv4")
	if err != nil {
		t.Errorf("eth3 should have ipv4: node=[%v] error=[%v]", node, err)
	}
	command.Dispatch(app, "no int eth3 ipv4", c, command.CONF, false)
	node, err = app.confRootCandidate.Get("interface eth3")
	if node != nil || err == nil {
		t.Errorf("eth3 should not exist: node=[%v] error=[%v]", node, err)
	}

	command.Dispatch(app, "int eth4 ipv4 addr 1.1.1.1/1", c, command.CONF, false)
	command.Dispatch(app, "int eth4 desc abc", c, command.CONF, false)
	node, err = app.confRootCandidate.Get("interface eth4 ipv4 address")
	if err != nil {
		t.Errorf("bad eth4 address: %v", err)
		return
	}
	if len(node.Children) != 1 {
		t.Errorf("wrong number of eth4 addresses (expected=1): %d", len(node.Children))
	}
	node, err = app.confRootCandidate.Get("interface eth4 description")
	if err != nil {
		t.Errorf("bad eth4 description: %v", err)
		return
	}
	command.Dispatch(app, "no int eth4 ipv4 addr", c, command.CONF, false)
	node, err = app.confRootCandidate.Get("interface eth4 ipv4")
	if node != nil || err == nil {
		t.Errorf("eth4 should not have ipv4: node=[%v] error=[%v]", node, err)
		return
	}
	node, err = app.confRootCandidate.Get("interface eth4")
	if node == nil || err != nil {
		t.Errorf("eth4 should exist: node=[%v] error=[%v]", node, err)
		return
	}

	f := func() {
		command.Dispatch(app, "interface eth5 ipv4 address 1.1.1.1/24", c, command.CONF, false)
	}
	noCmd, err := command.CmdFind(root, "no X", command.CONF, true)
	if err != nil {
		t.Errorf("could not find 'no' command: %v", err)
	}
	f()
	cmd := "no interface eth5 ipv4 address 1.1.1.1/24"
	if err := command.CmdNo(app, noCmd, cmd, c); err != nil {
		t.Errorf("cmd failed: [%s] error=[%v]", cmd, err)
	}
	f()
	cmd = "no interface eth5 ipv4 address"
	if err := command.CmdNo(app, noCmd, cmd, c); err != nil {
		t.Errorf("cmd failed: [%s] error=[%v]", cmd, err)
	}
	f()
	cmd = "no interface eth5 ipv4"
	if err := command.CmdNo(app, noCmd, cmd, c); err != nil {
		t.Errorf("cmd failed: [%s] error=[%v]", cmd, err)
	}
	f()
	cmd = "no interface eth5"
	if err := command.CmdNo(app, noCmd, cmd, c); err != nil {
		t.Errorf("cmd failed: [%s] error=[%v]", cmd, err)
	}
	f()
	cmd = "no interface"
	if err := command.CmdNo(app, noCmd, cmd, c); err != nil {
		t.Errorf("cmd failed: [%s] error=[%v]", cmd, err)
	}
	f()
	cmd = "no"
	if err := command.CmdNo(app, noCmd, cmd, c); err == nil {
		t.Errorf("bad cmd silently accepted: [%s]", cmd)
	}

}
コード例 #5
0
ファイル: cli_test.go プロジェクト: udhos/nexthop
func TestPrune(t *testing.T) {

	app := &testApp{
		cmdRoot:           &command.CmdNode{MinLevel: command.EXEC},
		confRootCandidate: &command.ConfNode{},
		confRootActive:    &command.ConfNode{},
	}

	hardware := fwd.NewDataplaneBogus()

	listInterfaces := func() ([]string, []string) {
		ifaces, vrfs, err := hardware.Interfaces()
		if err != nil {
			t.Errorf("hardware.Interfaces(): error: %v", err)
		}
		return ifaces, vrfs
	}
	listCommitId := func() []string {
		return []string{"BOGUS:TestPrune:listCommitId"}
	}
	command.LoadKeywordTable(listInterfaces, listCommitId)

	root := app.cmdRoot
	cmdNone := command.CMD_NONE
	cmdConf := command.CMD_CONF

	/*
		cmdBogus := func(ctx command.ConfContext, node *command.CmdNode, line string, c command.CmdClient) {
		}
	*/

	cmdSimpleSet := func(ctx command.ConfContext, node *command.CmdNode, line string, c command.CmdClient) {
		ctx.ConfRootCandidate().Set(node.Path, line)
	}

	path := "a b c d e f g h i j"
	jPath := "b c d e f g h i j"
	hPath := "b c d e f g h"
	gPath := "b c d e f g"
	fPath := "b c d e f"

	command.CmdInstall(root, cmdConf, path, command.CONF, cmdSimpleSet, command.ApplyBogus, "Teste prune A")
	command.CmdInstall(root, cmdConf, fPath, command.CONF, cmdSimpleSet, command.ApplyBogus, "Teste prune F")
	command.CmdInstall(root, cmdConf, jPath, command.CONF, cmdSimpleSet, command.ApplyBogus, "Teste prune J")
	command.CmdInstall(root, cmdNone, "no {ANY}", command.CONF, command.HelperNo, nil, "Remove a configuration item")

	c := &testClient{outputChannel: make(chan string)}
	close(c.outputChannel) // closed channel will break writers

	noCmd, err := command.CmdFind(root, "no X", command.CONF, true)
	if noCmd == nil || err != nil {
		t.Errorf("could not find 'no' command: %v", err)
		return
	}

	command.Dispatch(app, path, c, command.CONF, false)

	{
		node, err := app.confRootCandidate.Get(path)
		if node == nil || err != nil {
			t.Errorf("missing config node=[%s] error: %v", path, err)
			return
		}
		if node.Path != path {
			t.Errorf("config node mismatch want=[%s] got=[%s]", path, node.Path)
			return
		}
	}

	noPath := fmt.Sprintf("no %s", path)
	if err := command.CmdNo(app, noCmd, noPath, c); err != nil {
		t.Errorf("cmd failed: [%s] error=[%v]", noPath, err)
		return
	}

	{
		node, err := app.confRootCandidate.Get(path)
		if node != nil || err == nil {
			t.Errorf("unexpected config node=[%s] error: %v", path, err)
			return
		}
	}

	{
		p := "a"
		node, err := app.confRootCandidate.Get(p)
		if node != nil || err == nil {
			t.Errorf("unexpected config node=[%s] error: %v", p, err)
			return
		}
	}

	command.Dispatch(app, jPath, c, command.CONF, false)

	{
		node, err := app.confRootCandidate.Get(jPath)
		if node == nil || err != nil {
			t.Errorf("missing config node=[%s] error: %v", jPath, err)
			return
		}
		if node.Path != jPath {
			t.Errorf("config node mismatch want=[%s] got=[%s]", jPath, node.Path)
			return
		}
	}

	noH := fmt.Sprintf("no %s", hPath)
	if err := command.CmdNo(app, noCmd, noH, c); err != nil {
		t.Errorf("cmd failed: [%s] error=[%v]", noH, err)
		return
	}

	{
		node, err := app.confRootCandidate.Get(hPath)
		if node != nil || err == nil {
			t.Errorf("unexpected config node=[%s] error: %v", hPath, err)
			return
		}
	}

	{
		node, err := app.confRootCandidate.Get(gPath)
		if node != nil || err == nil {
			t.Errorf("unexpected config node=[%s] error: %v", gPath, err)
			return
		}
	}

	{
		node, err := app.confRootCandidate.Get(fPath)
		if node == nil || err != nil {
			t.Errorf("missing config node=[%s] error: %v", fPath, err)
			return
		}
		if node.Path != fPath {
			t.Errorf("config node mismatch want=[%s] got=[%s]", fPath, node.Path)
			return
		}
	}

}
コード例 #6
0
ファイル: rip_test.go プロジェクト: udhos/nexthop
func TestConf(t *testing.T) {

	app := &ripTestApp{
		cmdRoot:           &command.CmdNode{MinLevel: command.EXEC},
		confRootCandidate: &command.ConfNode{},
		confRootActive:    &command.ConfNode{},
	}

	hardware := fwd.NewDataplaneBogus()

	listInterfaces := func() ([]string, []string) {
		ifaces, vrfs, err := hardware.Interfaces()
		if err != nil {
			t.Errorf("hardware.Interfaces(): error: %v", err)
		}
		return ifaces, vrfs
	}
	listCommitId := func() []string {
		return []string{"BOGUS:rip.TestConf:listCommitId"}
	}
	command.LoadKeywordTable(listInterfaces, listCommitId)

	root := app.cmdRoot
	cmdNone := command.CMD_NONE
	cmdConf := command.CMD_CONF

	command.CmdInstall(root, cmdConf, "router rip", command.CONF, cmdRip, applyRip, "Enable RIP protocol")
	command.CmdInstall(root, cmdConf, "router rip network {NETWORK}", command.CONF, cmdRipNetwork, applyRipNet, "Insert network into RIP protocol")
	command.CmdInstall(root, cmdNone, "no {ANY}", command.CONF, command.HelperNo, nil, "Remove a configuration item")

	c := NewRipTestClient(func(string) {})

	r := "router rip"
	net := fmt.Sprintf("%s network", r)
	n1 := "1.1.1.0/24"
	n2 := "2.2.2.0/24"
	net1 := fmt.Sprintf("%s %s", net, n1)
	net2 := fmt.Sprintf("%s %s", net, n2)
	command.Dispatch(app, net1, c, command.CONF, false)
	command.Dispatch(app, net2, c, command.CONF, false)

	{
		node, err := app.confRootCandidate.Get(net)
		if node == nil || err != nil {
			t.Errorf("missing config node=[%s] error: %v", net, err)
			return
		}
		if node.Path != net {
			t.Errorf("config node mismatch want=[%s] got=[%s]", net, node.Path)
		}
		if len(node.Children) != 2 {
			t.Errorf("bad number of values want=2 got=%d", len(node.Children))
		}
		if v := command.LastToken(node.Children[0].Path); v != n1 {
			t.Errorf("unexpected 1st network want=%s got=%s", n1, v)
		}
		if v := command.LastToken(node.Children[1].Path); v != n2 {
			t.Errorf("unexpected 2nd network want=%s got=%s", n2, v)
		}
	}

	nonet1 := fmt.Sprintf("no %s", net1)
	nonet2 := fmt.Sprintf("no %s", net2)

	/*
		noCmd, err := command.CmdFind(root, "no X", command.CONF, true)
		if err != nil {
			t.Errorf("could not find 'no' command: %v", err)
			return
		}
	*/

	dumpConf(app.confRootCandidate, "conf1:")

	fmt.Printf("trying: [%s]\n", nonet1)
	if err := command.CmdNo(app, nil, nonet1, c); err != nil {
		t.Errorf("cmd failed: [%s] error=[%v]", nonet1, err)
		return
	}

	dumpConf(app.confRootCandidate, "conf2:")
	fmt.Printf("trying: [%s]\n", nonet2)
	if err := command.CmdNo(app, nil, nonet2, c); err != nil {
		t.Errorf("cmd failed: [%s] error=[%v]", nonet2, err)
		return
	}

	dumpConf(app.confRootCandidate, "conf3:")

	{
		node, err := app.confRootCandidate.Get(net)
		if node != nil || err == nil {
			t.Errorf("unexpected config node=[%s] error: %v", net, err)
		}
	}

	{
		node, err := app.confRootCandidate.Get(r)
		if node == nil || err != nil {
			t.Errorf("missing config node=[%s] error: %v", r, err)
		}
	}

}