/* Given a thing (field value in a struct), set the thing with the element in the map (key) the map value to the proper type. */ func set_value(thing reflect.Value, kind reflect.Kind, key string, tag_id string, pfx string, annon bool, m map[string]string) { if !thing.CanAddr() { // prevent stack dump return } switch kind { default: fmt.Fprintf(os.Stderr, "transform.mts: tagged sturct member cannot be converted from map: tag=%s kind=%v\n", key, thing.Kind()) case reflect.String: thing.SetString(m[key]) case reflect.Ptr: p := thing.Elem() // get the pointer value; allows us to suss the type if !p.IsValid() { // ptr is nill in the struct so we must allocate a pointer to 0 so it can be changed below thing.Set(reflect.New(thing.Type().Elem())) p = thing.Elem() } switch p.Kind() { case reflect.String: s := m[key] // copy it and then point to the copy thing.Set(reflect.ValueOf(&s)) case reflect.Int: i := clike.Atoi(m[key]) // convert to integer and then point at the value thing.Set(reflect.ValueOf(&i)) case reflect.Int64: i := clike.Atoi64(m[key]) thing.Set(reflect.ValueOf(&i)) case reflect.Int32: i := clike.Atoi32(m[key]) thing.Set(reflect.ValueOf(&i)) case reflect.Int16: i := clike.Atoi16(m[key]) thing.Set(reflect.ValueOf(&i)) case reflect.Int8: i := int8(clike.Atoi16(m[key])) thing.Set(reflect.ValueOf(&i)) case reflect.Uint: ui := clike.Atou(m[key]) thing.Set(reflect.ValueOf(&ui)) case reflect.Uint64: ui := clike.Atou64(m[key]) thing.Set(reflect.ValueOf(&ui)) case reflect.Uint32: ui := clike.Atou32(m[key]) thing.Set(reflect.ValueOf(&ui)) case reflect.Uint16: ui := clike.Atou16(m[key]) thing.Set(reflect.ValueOf(&ui)) case reflect.Uint8: ui := uint8(clike.Atou16(m[key])) thing.Set(reflect.ValueOf(&ui)) case reflect.Float64: fv := clike.Atof(m[key]) thing.Set(reflect.ValueOf(&fv)) case reflect.Float32: fv := float32(clike.Atof(m[key])) thing.Set(reflect.ValueOf(&fv)) case reflect.Bool: b := m[key] == "true" || m[key] == "True" || m[key] == "TRUE" thing.Set(reflect.ValueOf(&b)) case reflect.Struct: map_to_struct(m, p, p.Type(), tag_id, pfx) // recurse to process } case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8: thing.SetInt(clike.Atoi64(m[key])) case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8: thing.SetUint(uint64(clike.Atoi64(m[key]))) case reflect.Float64, reflect.Float32: thing.SetFloat(clike.Atof(m[key])) case reflect.Bool: thing.SetBool(m[key] == "true") case reflect.Map: new_map := reflect.MakeMap(thing.Type()) // create the map thing.Set(new_map) // put it in the struct idx := key + "/" // now populate the map ilen := len(idx) for k, _ := range m { // we could keep a separate list of keys, but for now this should do if strings.HasPrefix(k, key) { tokens := strings.Split(k[ilen:], "/") map_key := reflect.ValueOf(tokens[0]) // map key is everything past the tag to the next slant map_ele_type := new_map.Type().Elem() // the type of the element that the map references mthing := reflect.New(map_ele_type).Elem() // new returns pointer, so dereference with Elem() (value not type!) set_value(mthing, mthing.Kind(), idx+tokens[0], tag_id, idx+tokens[0]+"/", false, m) // put the value into the map thing new_map.SetMapIndex(map_key, mthing) // put it into the map (order IS important; add to map after recursion) //fmt.Fprintf( os.Stderr, "saving: %s thing-type=%s mthing=%s\n", map_key, thing.Type(), mthing ) } } case reflect.Slice: c := clike.Atoi(m[key+".cap"]) l := clike.Atoi(m[key+".len"]) thing.Set(reflect.MakeSlice(thing.Type(), l, c)) // create a new slice with the same len/cap that it had for j := 0; j < l; j++ { idx := fmt.Sprintf("%s/%d", key, j) set_value(thing.Index(j), thing.Type().Elem().Kind(), idx, tag_id, idx+"/", false, m) // populate each value of the slice up to len } case reflect.Struct: if annon { map_to_struct(m, thing, thing.Type(), tag_id, key) // anon structs share namespace, so prefix is the same } else { map_to_struct(m, thing, thing.Type(), tag_id, pfx) // dive to get the substruct adding a level to the prefix } } }
/* the main go routine to act on messages sent to our channel. We expect messages from the reservation manager, and from a tickler that causes us to evaluate the need to resize ovs queues. DSCP values: Dscp values range from 0-64 decimal, but when described on or by flow-mods are shifted two bits to the left. The send flow mod function will do the needed shifting so all values outside of that one funciton should assume/use decimal values in the range of 0-64. */ func Fq_mgr(my_chan chan *ipc.Chmsg, sdn_host *string) { var ( uri_prefix string = "" msg *ipc.Chmsg data []interface{} // generic list of data on some requests fdata *Fq_req // flow-mod request data qcheck_freq int64 = 5 hcheck_freq int64 = 180 host_list *string // current set of openstack real hosts ip2mac map[string]*string // translation from ip address to mac switch_hosts *string // from config file and overrides openstack list if given (mostly testing) ssq_cmd *string // command string used to set switch queues (from config file) send_all bool = false // send all flow-mods; false means send just ingress/egress and not intermediate switch f-mods alt_table int = DEF_ALT_TABLE // meta data marking table phost_suffix *string = nil // physical host suffix added to each host name in the list from openstack (config) //max_link_used int64 = 0 // the current maximum link utilisation ) fq_sheep = bleater.Mk_bleater(0, os.Stderr) // allocate our bleater and attach it to the master fq_sheep.Set_prefix("fq_mgr") tegu_sheep.Add_child(fq_sheep) // we become a child so that if the master vol is adjusted we'll react too // -------------- pick up config file data if there -------------------------------- if *sdn_host == "" { // not supplied on command line, pull from config if sdn_host = cfg_data["default"]["sdn_host"]; sdn_host == nil { // no default; when not in config, then it's turned off and we send to agent sdn_host = &empty_str } } if cfg_data["default"]["queue_type"] != nil { if *cfg_data["default"]["queue_type"] == "endpoint" { send_all = false } else { send_all = true } } if p := cfg_data["default"]["alttable"]; p != nil { // this is the base; we use alt_table to alt_table + (n-1) when we need more than 1 table alt_table = clike.Atoi(*p) } if cfg_data["fqmgr"] != nil { // pick up things in our specific setion if dp := cfg_data["fqmgr"]["ssq_cmd"]; dp != nil { // set switch queue command ssq_cmd = dp } /* if p := cfg_data["fqmgr"]["default_dscp"]; p != nil { // this is a single value and should not be confused with the dscp list in the default section of the config dscp = clike.Atoi( *p ) } */ if p := cfg_data["fqmgr"]["queue_check"]; p != nil { // queue check frequency from the control file qcheck_freq = clike.Atoi64(*p) if qcheck_freq < 5 { qcheck_freq = 5 } } if p := cfg_data["fqmgr"]["host_check"]; p != nil { // frequency of checking for new _real_ hosts from openstack hcheck_freq = clike.Atoi64(*p) if hcheck_freq < 30 { hcheck_freq = 30 } } if p := cfg_data["fqmgr"]["switch_hosts"]; p != nil { switch_hosts = p } if p := cfg_data["fqmgr"]["verbose"]; p != nil { fq_sheep.Set_level(uint(clike.Atoi(*p))) } if p := cfg_data["fqmgr"]["phost_suffix"]; p != nil { // suffix added to physical host strings for agent commands if *p != "" { phost_suffix = p fq_sheep.Baa(1, "physical host names will be suffixed with: %s", *phost_suffix) } } } // ----- end config file munging --------------------------------------------------- //tklr.Add_spot( qcheck_freq, my_chan, REQ_SETQUEUES, nil, ipc.FOREVER ); // tickle us every few seconds to adjust the ovs queues if needed if switch_hosts == nil { tklr.Add_spot(2, my_chan, REQ_CHOSTLIST, nil, 1) // tickle once, very soon after starting, to get a host list tklr.Add_spot(hcheck_freq, my_chan, REQ_CHOSTLIST, nil, ipc.FOREVER) // tickles us every once in a while to update host list fq_sheep.Baa(2, "host list will be requested from openstack every %ds", hcheck_freq) } else { host_list = switch_hosts fq_sheep.Baa(0, "static host list from config used for setting OVS queues: %s", *host_list) } if sdn_host != nil && *sdn_host != "" { uri_prefix = fmt.Sprintf("http://%s", *sdn_host) } fq_sheep.Baa(1, "flowmod-queue manager is running, sdn host: %s", *sdn_host) for { msg = <-my_chan // wait for next message msg.State = nil // default to all OK fq_sheep.Baa(3, "processing message: %d", msg.Msg_type) switch msg.Msg_type { case REQ_GEN_FMOD: // generic fmod; just pass it along w/o any special handling if msg.Req_data != nil { fdata = msg.Req_data.(*Fq_req) // pointer at struct with all of our expected goodies send_gfmod_agent(fdata, ip2mac, host_list, phost_suffix) } case REQ_BWOW_RESERVE: // oneway bandwidth flow-mod generation msg.Response_ch = nil // nothing goes back from this fdata = msg.Req_data.(*Fq_req) // pointer at struct with all of the expected goodies send_bwow_fmods(fdata, ip2mac, phost_suffix) case REQ_BW_RESERVE: // bandwidth endpoint flow-mod creation; single agent script creates all needed fmods fdata = msg.Req_data.(*Fq_req) // pointer at struct with all of the expected goodies send_bw_fmods(fdata, ip2mac, phost_suffix) msg.Response_ch = nil // nothing goes back from this case REQ_IE_RESERVE: // proactive ingress/egress reservation flowmod (this is likely deprecated as of 3/21/2015 -- resmgr invokes the bw_fmods script via agent) fdata = msg.Req_data.(*Fq_req) // user view of what the flow-mod should be if uri_prefix != "" { // an sdn controller -- skoogi -- is enabled msg.State = gizmos.SK_ie_flowmod(&uri_prefix, *fdata.Match.Ip1, *fdata.Match.Ip2, fdata.Expiry, fdata.Espq.Queuenum, fdata.Espq.Switch, fdata.Espq.Port) if msg.State == nil { // no error, no response to requestor fq_sheep.Baa(2, "proactive reserve successfully sent: uri=%s h1=%s h2=%s exp=%d qnum=%d swid=%s port=%d dscp=%d", uri_prefix, fdata.Match.Ip1, fdata.Match.Ip2, fdata.Expiry, fdata.Espq.Queuenum, fdata.Espq.Switch, fdata.Espq.Port) msg.Response_ch = nil } else { // do we need to suss out the id and mark it failed, or set a timer on it, so as not to flood reqmgr with errors? fq_sheep.Baa(1, "ERR: proactive reserve failed: uri=%s h1=%s h2=%s exp=%d qnum=%d swid=%s port=%d [TGUFQM008]", uri_prefix, fdata.Match.Ip1, fdata.Match.Ip2, fdata.Expiry, fdata.Espq.Queuenum, fdata.Espq.Switch, fdata.Espq.Port) } } else { // q-lite now generates one flowmod in each direction because of the ITONS requirements if send_all || fdata.Espq.Queuenum > 1 { // if sending all fmods, or this has a non-intermediate queue cdata := fdata.Clone() // copy so we can alter w/o affecting sender's copy if cdata.Espq.Port == -128 { // we'll assume in this case that the switch given is the host name and we need to set the switch to br-int swid := "br-int" cdata.Swid = &swid } if cdata.Resub == nil { resub_list := "" // resub to alternate table to set a meta mark, then to table 0 to hit openstack junk if cdata.Single_switch || fdata.Dir_in { // must use the base table for inbound traffic OR same switch traffic (bug 2015/1/26) resub_list = fmt.Sprintf("%d 0", alt_table) // base alt_table is for 'local' traffic (trafic that doesn't go through br-rl } else { resub_list = fmt.Sprintf("%d 0", alt_table+1) // base+1 is for OUTBOUND only traffic that must go through the rate limiting bridge } cdata.Resub = &resub_list } meta := "0x00/0x07" // match-value/mask; match only when meta neither of our two bits, nor the agent bit (0x04) are set cdata.Match.Meta = &meta if fdata.Dir_in { // inbound to this switch we need to revert dscp from our settings to the 'origianal' settings if cdata.Single_switch { cdata.Match.Dscp = -1 // there is no match if both on same switch send_gfmod_agent(cdata, ip2mac, host_list, phost_suffix) } else { cdata.Match.Dscp = cdata.Dscp // match the dscp that was added on ingress if !cdata.Dscp_koe { // dropping the value on exit cdata.Action.Dscp = 0 // set action to turn it off, otherwise we let it ride (no overt action) } send_gfmod_agent(cdata, ip2mac, host_list, phost_suffix) } } else { // outbound from this switch set the dscp value specified on the reservation cdata.Match.Dscp = -1 // on outbound there is no dscp match, ensure this is off if cdata.Single_switch { send_gfmod_agent(cdata, ip2mac, host_list, phost_suffix) // in single switch mode there is no dscp value needed } else { cdata.Action.Dscp = cdata.Dscp // otherwise set the value and send send_gfmod_agent(cdata, ip2mac, host_list, phost_suffix) } } } msg.Response_ch = nil } case REQ_ST_RESERVE: // reservation fmods for traffic steering msg.Response_ch = nil // for now, nothing goes back if msg.Req_data != nil { fq_data := msg.Req_data.(*Fq_req) // request data if uri_prefix != "" { // an sdn controller -- skoogi -- is enabled (not supported) fq_sheep.Baa(0, "ERR: steering reservations are not supported with skoogi (SDNC); no flow-mods pushed") } else { send_stfmod_agent(fq_data, ip2mac, host_list) } } else { fq_sheep.Baa(0, "CRI: missing data on st-reserve request to fq-mgr") } case REQ_SK_RESERVE: // send a reservation to skoogi data = msg.Req_data.([]interface{}) // msg data expected to be array of interface: h1, h2, expiry, queue h1/2 must be IP addresses if uri_prefix != "" { fq_sheep.Baa(2, "msg to reserve: %s %s %s %d %d", uri_prefix, data[0].(string), data[1].(string), data[2].(int64), data[3].(int)) msg.State = gizmos.SK_reserve(&uri_prefix, data[0].(string), data[1].(string), data[2].(int64), data[3].(int)) } else { fq_sheep.Baa(1, "reservation not sent, no sdn-host defined: %s %s %s %d %d", uri_prefix, data[0].(string), data[1].(string), data[2].(int64), data[3].(int)) } case REQ_SETQUEUES: // request from reservation manager which indicates something changed and queues need to be reset qlist := msg.Req_data.([]interface{})[0].([]string) if ssq_cmd != nil { adjust_queues(qlist, ssq_cmd, host_list) // if writing to a file and driving a local script } else { adjust_queues_agent(qlist, host_list, phost_suffix) // if sending json to an agent } case REQ_CHOSTLIST: // this is tricky as it comes from tickler as a request, and from osifmgr as a response, be careful! msg.Response_ch = nil // regardless of source, we should not reply to this request if msg.State != nil || msg.Response_data != nil { // response from ostack if with list or error if msg.Response_data.(*string) != nil { hls := strings.TrimLeft(*(msg.Response_data.(*string)), " \t") // ditch leading whitespace hl := &hls if *hl != "" { host_list = hl // ok to use it if phost_suffix != nil { fq_sheep.Baa(2, "host list from osif before suffix added: %s", *host_list) host_list = add_phost_suffix(host_list, phost_suffix) // in some cases ostack sends foo, but we really need to use foo-suffix (sigh) } send_hlist_agent(host_list) // send to agent_manager fq_sheep.Baa(2, "host list received from osif: %s", *host_list) } else { fq_sheep.Baa(1, "host list received from osif was discarded: ()") } } else { fq_sheep.Baa(0, "WRN: no data from openstack; expected host list string [TGUFQM009]") } } else { req_hosts(my_chan, fq_sheep) // send requests to osif for data } case REQ_IP2MACMAP: // a new map from osif if msg.Req_data != nil { newmap := msg.Req_data.(map[string]*string) if len(newmap) > 0 { ip2mac = newmap // safe to replace fq_sheep.Baa(2, "ip2mac translation received from osif: %d elements", len(ip2mac)) } else { if ip2mac != nil { fq_sheep.Baa(2, "ip2mac translation received from osif: 0 elements -- kept old table with %d elements", len(ip2mac)) } else { fq_sheep.Baa(2, "ip2mac translation received from osif: 0 elements -- no existing table to keep") } } } else { fq_sheep.Baa(0, "WRN: no data from osif (nil map); expected ip2mac translation map [TGUFQM010]") } msg.State = nil // state is always good default: fq_sheep.Baa(1, "unknown request: %d", msg.Msg_type) msg.Response_data = nil if msg.Response_ch != nil { msg.State = fmt.Errorf("unknown request (%d)", msg.Msg_type) } } fq_sheep.Baa(3, "processing message complete: %d", msg.Msg_type) if msg.Response_ch != nil { // if a reqponse channel was provided fq_sheep.Baa(3, "sending response: %d", msg.Msg_type) msg.Response_ch <- msg // send our result back to the requestor } } }