diff --git a/clab/config/template.go b/clab/config/template.go index ab2610f5a..1e63c97f4 100644 --- a/clab/config/template.go +++ b/clab/config/template.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "path/filepath" + "strconv" "strings" "text/template" @@ -171,7 +172,20 @@ func (c *ConfigSnippet) String() string { return fmt.Sprintf("%s: %s %d lines of config", c.TargetNode.LongName, c.source, len(c.Config)) } +func typeof(val interface{}) string { + switch val.(type) { + case string: + return "string" + case int: + return "int" + } + return "" +} + var funcMap = map[string]interface{}{ + "expect": func(val interface{}, format interface{}) (interface{}, error) { + return nil, nil + }, "require": func(val interface{}) (interface{}, error) { if val == nil { return nil, errors.New("required value not set") @@ -192,21 +206,80 @@ var funcMap = map[string]interface{}{ if def == nil { return nil, fmt.Errorf("default value expected") } - switch val.(type) { + + switch v := val.(type) { case string: - if val == "" { + if v == "" { return def, nil } - default: - if val == nil { + case bool: + if !v { return def, nil } } + if val == nil { + return def, nil + } + + // If we have a input value, do some type checking + tval, tdef := typeof(val), typeof(def) + if tval == "string" && tdef == "int" { + if _, err := strconv.Atoi(val.(string)); err == nil { + tval = "int" + } + } + if tdef != tval { + return val, fmt.Errorf("expected type %v, got %v (value=%v)", tdef, tval, val) + } + + // Return the value return val, nil }, "contains": func(str interface{}, substr interface{}) (interface{}, error) { return strings.Contains(fmt.Sprintf("%v", str), fmt.Sprintf("%v", substr)), nil }, + "split": func(val interface{}, sep interface{}) (interface{}, error) { + // Start and end values + if val == nil { + return []interface{}{}, nil + } + s := fmt.Sprintf("%v", sep) + if sep == nil { + s = " " + } + + v := fmt.Sprintf("%v", val) + + res := strings.Split(v, s) + r := make([]interface{}, len(res)) + for i, p := range res { + r[i] = p + } + return r, nil + }, + "join": func(val interface{}, sep interface{}) (interface{}, error) { + s := fmt.Sprintf("%s", sep) + if sep == nil { + s = " " + } + // Start and end values + switch v := val.(type) { + case []interface{}: + if val == nil { + return "", nil + } + res := make([]string, len(v)) + for i, v := range v { + res[i] = fmt.Sprintf("%v", v) + } + return strings.Join(res, s), nil + case []string: + return strings.Join(v, s), nil + case []int, []int16, []int32: + return strings.Trim(strings.Replace(fmt.Sprint(v), " ", s, -1), "[]"), nil + } + return nil, fmt.Errorf("expected array [], got %v", val) + }, "slice": func(val interface{}, start interface{}, end interface{}) (interface{}, error) { // Start and end values var s, e int diff --git a/clab/config/template_test.go b/clab/config/template_test.go index 0de3cfc9b..b9a434182 100644 --- a/clab/config/template_test.go +++ b/clab/config/template_test.go @@ -10,30 +10,55 @@ func TestFuncMapDefault(t *testing.T) { // parameters & return tSet := map[string][][]interface{}{ "default": { - {nil, 1, 1}, + {0, nil, nil, true}, {5, 1, 5}, + {5, "1", 5, "invalid types"}, + {"a", 1, "a", "invalid types"}, {"", "1", "1"}, - {nil, nil, fmt.Errorf("")}, + {nil, nil, nil, true}, }, "contains": { {"aa.", ".", true}, {"ss", ".", false}, + }, + "split": { + {"a.a", ".", "[a a]"}, + {"a bb", nil, "[a bb]"}, + {nil, nil, "[]"}, + {nil, ".", "[]"}, + }, + "join": { + {[]interface{}{"a", "b"}, ".", "a.b"}, + {[]string{"a", "b"}, ".", "a.b"}, + {[]int{1, 2}, ".", "1.2"}, }} for name, set := range tSet { fn := funcMap[name].(func(interface{}, interface{}) (interface{}, error)) for _, p := range set { - exp := p[len(p)-1] - var expe error - switch v := exp.(type) { - case error: - expe = v - exp = nil - } + // Execute the funciton res, err := fn(p[0], p[1]) - if res != exp || (err != nil && expe == nil) { - t.Errorf("%v expected %v got %v error %v err: %v", p, exp, res, expe, err) + + // expect return value + exp := p[2] + exp_err := len(p) == 4 + + // Check errors + if err != nil && !exp_err { + t.Errorf("%v no err expected, err: %v", p, err) + } + if err == nil && exp_err { + t.Errorf("%v err expected, non found", p) + } + + // Check value + if res != exp { + // allow arrays (match on string only) + if fmt.Sprintf("%v", res) == fmt.Sprintf("%v", exp) { + continue + } + t.Errorf("%v expected %v got %v", p, exp, res) } } } diff --git a/templates/vr-sros/base-node.tmpl b/templates/vr-sros/base-node.tmpl index ecd150c79..8bf29749e 100644 --- a/templates/vr-sros/base-node.tmpl +++ b/templates/vr-sros/base-node.tmpl @@ -1,3 +1,6 @@ +{{ expect .systemip "ip" }} +{{ expect .sid_idx "0-1000" }} + /configure system login-control idle-timeout 1440 /configure apply-groups ["baseport"] @@ -66,13 +69,13 @@ /configure router autonomous-system {{ default .as_number "64500" }} - mpls-labels sr-labels start {{ default .sid_start "19000" }} end {{ default .sid_end "30000" }} + mpls-labels sr-labels start {{ default .sid_start 19000 }} end {{ default .sid_end 30000 }} -/configure router isis {{ default .isis_iid "0" }} - area-address 49.0000.000{{ default .isis_iid "0" }} +/configure router isis {{ default .isis_iid 0 }} + area-address 49.0000.000{{ default .isis_iid 0 }} level-capability 2 level 2 wide-metrics-only - interface "system" ipv4-node-sid index {{ .sid_idx }} + interface "system" ipv4-node-sid index {{ require .sid_idx }} #database-export igp-identifier {{ default .isis_iid "0" }} bgp-ls-identifier value {{ default .isis_iid "0" }} traffic-engineering advertise-router-capability area