# # Copyright 2010-2013 the Boeing Company. # See the LICENSE file included in this distribution. # # author: Jeff Ahrenholz # # GUI support for node types and profiles. # global execMode if { $execMode == "interactive" } { package require Ttk } # these are the default node types when nodes.conf does not exist # index {name normal-icon tiny-icon services type metadata} array set g_node_types_default { 1 {router router.gif router.gif {zebra OSPFv2 OSPFv3 vtysh IPForward} \ netns {built-in type for routing}} 2 {host host.gif host.gif {DefaultRoute SSH} \ netns {built-in type for servers}} 3 {PC pc.gif pc.gif {DefaultRoute} \ netns {built-in type for end hosts}} 4 {mdr mdr.gif mdr.gif {zebra OSPFv3MDR vtysh IPForward} \ netns {built-in type for wireless routers}} 5 {prouter router_green.gif router_green.gif \ {zebra OSPFv2 OSPFv3 vtysh IPForward} \ physical {built-in type for physical nodes}} 6 {xen xen.gif xen.gif {zebra OSPFv2 OSPFv3 vtysh IPForward} \ xen {built-in type for Xen PVM domU router}} } # possible machine types for nodes set MACHINE_TYPES "netns physical xen" # array populated from nodes.conf file array set g_node_types { } # # Load the nodes.conf file into the g_nodes array # proc loadNodesConf { } { global CONFDIR g_node_types g_node_types_default MACHINE_TYPES set confname "$CONFDIR/nodes.conf" if { [catch { set f [open $confname r] } ] } { puts "Creating a default $confname" unset g_node_types array set g_node_types [array get g_node_types_default] writeNodesConf return } array unset g_nodes while { [ gets $f line ] >= 0 } { if { [string range $line 0 0] == "#" } { continue } ;# skip comments # fix-up 5-element list to include node type if { [llength $line] == 2 } { set idx [lindex $line 0]; set data [lindex $line 1] if { [llength $data] == 5 } { set data [linsert $data 4 [lindex $MACHINE_TYPES 0]] set line [list $idx $data] } } # load into array of nodes if { [catch {array set g_node_types $line} e] } { puts "Error reading $confname line '$node': $e" } } close $f checkNodeTypes true } # look for missing default node types; exit if fatal flag is true; return a # string for the first missing type proc checkNodeTypes { fatal } { global g_node_types_default set names [getNodeTypeNames] foreach i [lsort [array names g_node_types_default]] { set name [lindex $g_node_types_default($i) 0] if { [lsearch -exact $names $name] < 0 } { puts "error: missing built-in node type '$name'!" puts "move your ~/.core/nodes.conf file to re-create the defaults" if { $fatal } { exit } else { return $name } } } return "" } # # Write the nodes.conf file from the g_nodes array. # proc writeNodesConf { } { global CONFDIR g_node_types set confname "$CONFDIR/nodes.conf" if { [catch { set f [open "$confname" w] } ] } { puts "***Warning: could not write nodes file: $confname" return } set header "# nodes.conf: CORE node templates customization file." set header "$header\n# format: index {name normal-icon tiny-icon services" set header "$header type metadata}" puts $f $header foreach i [lsort -integer [array names g_node_types]] { puts $f "$i { $g_node_types($i) }" } close $f } # return a list of names of node types proc getNodeTypeNames {} { global g_node_types set names "" foreach i [lsort -integer [array names g_node_types]] { set node_type_data $g_node_types($i) set name [lindex $node_type_data 0] lappend names $name } return $names } proc isDefaultNodeType { nodetype } { global g_node_types_default foreach i [lsort [array names g_node_types_default]] { set name [lindex $g_node_types_default($i) 0] if { $nodetype == $name } { return true} } return false } # return the image path name for the specified node type # size should equal "tiny" or "normal" proc getNodeTypeImage { type size } { global g_node_types CORE_DATA_DIR foreach i [lsort -integer [array names g_node_types]] { set node_type_data $g_node_types($i) if { [lindex $node_type_data 0] == $type } { if { $size == "tiny" } { set imgf [lindex $node_type_data 2] } else { set imgf [lindex $node_type_data 1] } # if the image has no path, assume it can be # found in $CORE_DATA_DIR/icons/tiny if { [string first "/" $imgf] < 0 } { set imgf "$CORE_DATA_DIR/icons/$size/$imgf" } return $imgf } } return "" } # return the index in the global array for the given node type proc getNodeTypeIndex { type } { global g_node_types foreach i [lsort -integer [array names g_node_types]] { set node_type_data $g_node_types($i) if { [lindex $node_type_data 0] == $type } { return $i } } return -1 } # return the default services for this node type proc getNodeTypeServices { type } { global g_node_types foreach i [lsort -integer [array names g_node_types]] { set node_type_data $g_node_types($i) if { [lindex $node_type_data 0] == $type } { return [lindex $node_type_data 3] } } return "" } # return the machine type (e.g. netns, physical, xen) of the currently selected # node type from the toolbar proc getNodeTypeMachineType { type } { global MACHINE_TYPES g_node_types set default_machine_type [lindex $MACHINE_TYPES 0] set i [getNodeTypeIndex $type] if { $i < 0 } { return $default_machine_type }; # failsafe return [lindex $g_node_types($i) 4] } proc getNodeTypeProfile { type } { global g_node_types foreach i [lsort -integer [array names g_node_types]] { set node_type_data $g_node_types($i) if { [lindex $node_type_data 0] == $type } { if {[llength $node_type_data] >= 7 } { return [lindex $node_type_data 6] } break ;# profile may be empty } } return "" } # return the machine type (e.g. netns, physical, xen) of the currently selected # node type from the toolbar proc getNodeTypeMachineType { type } { global MACHINE_TYPES g_node_types set default_machine_type [lindex $MACHINE_TYPES 0] set i [getNodeTypeIndex $type] if { $i < 0 } { return $default_machine_type }; # failsafe return [lindex $g_node_types($i) 4] } # Helper for add/delete button next to a list/combo box; from is the text entry # from which the value is copied, and to is the list/combo box where the value # is inserted upon add proc listboxAddDelHelper { cmd from to combo } { set current [$from get] ;# current text from entry or combo if { $combo } { set values [$to cget -values] set i [lsearch -exact $values $current] } if { $cmd == "add" } { if { $combo } { if { $i != -1 } { return } ;# item already exists lappend values $current $to configure -values $values } else { $to insert end $current } } elseif { $cmd == "del" } { if { $combo } { # search combo box values for current text if { $i == -1 } { return } ;# item doesn't exist set values [lreplace $values $i $i] $to configure -values $values } else { set values [$to curselection] if { $values == "" } { return } ;# no current selection $to delete [lindex $values 0] ;# delete only first selected item } $from delete 0 end ;# clear text entry/combo on delete } } # helper to populate a text entry when a listbox selection has changed proc listboxSelect { lb ent } { set i [$lb curselection] $ent delete 0 end if { $i == "" } { return } $ent insert 0 [$lb get $i] } # # Popup a profile configuration dialog box, using popupCapabilityConfig # proc popupNodeProfileConfig { channel node model types values captions bitmap possible_values groups session opaque } { global g_node_types set opaque_items [split $opaque :] if { [llength $opaque_items] != 2 } { puts "warning: received unexpected opaque data in conf message!" return } set nodetype [lindex $opaque_items 1] # check if we already have config for this profile, replacing values set existing_values [getNodeTypeProfile $nodetype] if { $existing_values != "" } { if { [llength $existing_values] == [llength $values] } { set values $existing_values } else { ;# this accommodates changes to models puts "warning: discarding stale profile for $model from nodes.conf" } } popupCapabilityConfig $channel $node $model $types $values \ $captions $bitmap $possible_values $groups } proc popupNodeProfileConfigApply { vals } { global g_node_types g_node_type_services_hint set type $g_node_type_services_hint set idx [getNodeTypeIndex $type] if { $idx < 0 } { puts "warning: skipping unknown node type $type" } else { set typedata $g_node_types($idx) if { [llength $typedata] < 7 } { set typedata [linsert $typedata 6 $vals] ;# no profile in list } else { set typedata [lreplace $typedata 6 6 $vals] ;# update the profile } array set g_node_types [list $idx $typedata] } # node type will be used in sendConfReplyMessage opaque data return "model:$type" } array set g_nodes_button_tooltips { add "add a new node type" save "apply changes to this node type" del "remove the selected node type" up "move the node type up in the list" down "move the selected node type down in the list" } # show the CORE Node Types configuration dialog # this allows the user to define new node types having different names, icons, # and default set of services proc popupNodesConfig {} { global g_nodes_types g_nodes_button_tooltips MACHINE_TYPES g_machine_type global CORE_DATA_DIR set wi .nodesConfig catch {destroy $wi} toplevel $wi wm transient $wi . wm resizable $wi 0 1 wm title $wi "CORE Node Types" # list of nodes labelframe $wi.s -borderwidth 0 -text "Node Types" listbox $wi.s.nodes -selectmode single -height 5 -width 15 \ -yscrollcommand "$wi.s.nodes_scroll set" -exportselection 0 scrollbar $wi.s.nodes_scroll -command "$wi.s.nodes yview" pack $wi.s.nodes $wi.s.nodes_scroll -fill y -side left pack $wi.s -padx 4 -pady 4 -fill both -side top -expand true # image button bar frame $wi.bbar set buttons "add save del" foreach b $buttons { # re-use images from the plugin dialog global plugin_img_$b button $wi.bbar.$b -image [set plugin_img_$b] \ -command "nodesConfigHelper $wi $b" pack $wi.bbar.$b -side left balloon $wi.bbar.$b $g_nodes_button_tooltips($b) } pack $wi.bbar -padx 4 -pady 4 -fill x -side top # up/down buttons foreach b {up down} { set fn "$CORE_DATA_DIR/icons/tiny/arrow.${b}.gif" set img$b [image create photo -file $fn] button $wi.bbar.$b -image [set img${b}] \ -command "nodesConfigHelper $wi $b" pack $wi.bbar.$b -side left balloon $wi.bbar.$b $g_nodes_button_tooltips($b) } # node type edit area frame $wi.s.edit -borderwidth 4 frame $wi.s.edit.0 label $wi.s.edit.0.namelab -text "Name" entry $wi.s.edit.0.name -bg white -width 20 pack $wi.s.edit.0.namelab $wi.s.edit.0.name -side left frame $wi.s.edit.1 label $wi.s.edit.1.iconlab -text "Icon" entry $wi.s.edit.1.icon -bg white -width 25 button $wi.s.edit.1.filebtn -text "..." \ -command "nodesConfigImgDialog $wi $wi.s.edit.1.icon normal" pack $wi.s.edit.1.iconlab $wi.s.edit.1.icon $wi.s.edit.1.filebtn -side left bind $wi.s.edit.1.icon "nodesConfigImg $wi" canvas $wi.s.edit.0.c -width 60 -height 60 # -bg white pack $wi.s.edit.0.c -side right -padx 10 bind $wi.s.edit.0.c