/* Build a set of openstack objects for each project (tenant) that we have access to. Retuns the list of creds and both ID->project and project->ID maps We build a new map each time, copying existing references, so that if a parallel thread has a copy and is working from it a change to the map isn't disruptive. This function also sets a reference ("_ref_") entry in the map which can be used to pull an entry out when any of them will do. NOTE: All errors will be logged, but only the first error will be returned to the caller. */ func refresh_creds(admin *ostack.Ostack, old_list map[string]*ostack.Ostack, id2pname map[string]*string) (creds map[string]*ostack.Ostack, gerr error) { var ( err error ) creds = make(map[string]*ostack.Ostack) // new map to fill in if old_list == nil { old_list = creds } for k, v := range id2pname { // run the list of projects and add creds to the map if we don't have them if old_list[*v] == nil { osif_sheep.Baa(1, "adding creds for: %s/%s", k, *v) creds[*v], err = admin.Dup(v) // duplicate creds for this project and then authorise to get a token if err != nil { osif_sheep.Baa(1, "WRN: unable to authorise credentials for project: %s [TGUOSI008]", *v) delete(creds, *v) } if gerr == nil { // ensure error captured for return if last in list is good gerr = err } } else { creds[*v] = old_list[*v] // reuse the data osif_sheep.Baa(2, "reusing credentials for: %s", *v) } if creds["_ref"] == nil && creds[*v] != nil { // set the quick reference key creds["_ref_"] = creds[*v] } } return }
/* Fetch new maps and update the project list. Returns: osack reference map project name to id map id to project name map On error err is set, and nil values returned for conversion maps and the old os_list is returned */ func update_project(os_admin *ostack.Ostack, old_os_refs map[string]*ostack.Ostack, os_projects map[string]*osif_project, old_pname2id map[string]*string, old_id2pname map[string]*string, update_list bool) ( os_refs map[string]*ostack.Ostack, id2pname map[string]*string, pname2id map[string]*string) { new_name2id, new_id2pname, err := os_admin.Map_tenants() // fetch new maps, overwrite only if no errors if err == nil { pname2id = new_name2id id2pname = new_id2pname } else { osif_sheep.Baa(1, "WRN: unable to get tenant name/ID translation data: %s [TGUOSI010]", err) return old_os_refs, old_pname2id, old_id2pname } if update_list { // asked to update the os_refs too os_refs, _ = refresh_creds(os_admin, old_os_refs, id2pname) // periodic update of project cred list add2projects(os_projects, os_refs, pname2id, 2) // add refernces to the projects list if osif_sheep.Would_baa(2) { for _, v := range os_projects { osif_sheep.Baa(2, "update project sees: %s", *v.name) } } } else { os_refs = old_os_refs } osif_sheep.Baa(1, "credentials were updated from openstack") return os_refs, pname2id, id2pname }
/* Given a token, return true if the token is valid for one of the roles listed in role. Role is a list of space separated role names. Token is expected to be token/projectr; we will test only with the indicated project. 2015.12.09 - We will only accept token/project strings as trying a token against every proejct that Tegu knows about doesn't scale in the openstack world, and openstack doesn't make a 'generic' crack function available. If a string is passed as token and _NOT_ token/project it will be rejected with an appropriate error. WARNING: If the token passed to us to crack is not a valid token the error message that openstack returns might be very misleading, suggesting that Tegu is not authorised to crack the token: Unauthorized (401): The request you have made requires authentication. This is not cool openstack. The token is invalid, full stop and you should say so. */ func has_any_role(os_refs map[string]*ostack.Ostack, admin *ostack.Ostack, token *string, roles *string) (userproj string, err error) { rtoks := strings.Split(*roles, ",") // simple tokenising of role list userproj = "" if strings.Contains(*token, "/") { // assume it's token/project (could also be tok/proj/junk) const p int = 1 // order in split tokens (project) const t int = 0 // order in split tokens (actual token) toks := strings.Split(*token, "/") if toks[p] == "" { osif_sheep.Baa(2, "has_any_role: project/token had empty project") return "", fmt.Errorf("project portion of token/project was empty") } stuff, err := admin.Crack_ptoken(&toks[t], &toks[p], false) // crack user info based on project and token if err == nil { state := gizmos.Map_has_any(stuff.Roles, rtoks) // true if any from rtoks list matches any in Roles if state { osif_sheep.Baa(2, "has_any_role: token/project validated for roles: %s", *roles) return (stuff.User + "," + stuff.TenantId), nil } else { err = fmt.Errorf("none matched") } } osif_sheep.Baa(2, "has_any_role: token/project %s/%s not valid for roles: %s: %s (caution, 401 error from openstack is misleading if it suggests the request requires auth)", toks[0], toks[1], *roles, err) return "", fmt.Errorf("has_any_role: token/project not valid for roles: %s: %s", *roles, err) } return "", fmt.Errorf("has_any_role: rejected: data was NOT of the form token/project") }
/* Given a token, return true if the token is valid for one of the roles listed in role. Role is a list of space separated role names. If token is actually tokken/project, then we will test only with the indicated project. Otherwise we will test the token against every project we know about and return true if any of the roles in the list is defined for the user in any project. This _is_ needed to authenticate Tegu requests which are not directly project oriented (e.g. set capacities, graph, etc.), so it is legitimate for a token to be submitted without a leading project/ string. */ func has_any_role(os_refs map[string]*ostack.Ostack, admin *ostack.Ostack, token *string, roles *string) (has_role bool, err error) { rtoks := strings.Split(*roles, ",") // simple tokenising of role list has_role = false if strings.Contains(*token, "/") { // assume it's token/project const p int = 1 // order in split tokens (project) const t int = 0 // order in split tokens (actual token) toks := strings.Split(*token, "/") if toks[p] == "" { osif_sheep.Baa(2, "has_any_role: project/token had empty project") return false, fmt.Errorf("project portion of token/project was empty") } stuff, err := admin.Crack_ptoken(&toks[t], &toks[p], false) // crack user info based on project and token if err == nil { state := gizmos.Map_has_any(stuff.Roles, rtoks) // true if any from rtoks list matches any in Roles if state { osif_sheep.Baa(2, "has_any_role: token/project validated for roles: %s", *roles) return true, nil } else { err = fmt.Errorf("none matched") } } osif_sheep.Baa(2, "has_any_role: token/project not valid for roles: %s: %s", *roles, err) return false, fmt.Errorf("has_any_role: token/project not valid for roles: %s: %s", roles, err) } for _, v := range os_refs { pname, _ := v.Get_project() osif_sheep.Baa(2, "has_any_role: checking %s", *pname) stuff, err := admin.Crack_ptoken(token, pname, false) // return a stuff struct with details about the token if err == nil { err = fmt.Errorf("role not defined; %s", *roles) // asume error state := gizmos.Map_has_any(stuff.Roles, rtoks) // true if any role token matches anything from ostack if state { osif_sheep.Baa(2, "has_any_role: verified in %s", *pname) return true, nil } } else { osif_sheep.Baa(2, "has_any_role: crack failed for project=%s: %s", *pname, err) } } if err == nil { err = fmt.Errorf("undetermined reason") } osif_sheep.Baa(1, "unable to verify role: %s: %s", *roles, err) return false, err }
/* Verifies that the token passed in is a valid token for the default user (a.k.a. the tegu admin) given in the config file. Returns "ok" (err is nil) if it is good, and an error otherwise. */ func validate_admin_token(admin *ostack.Ostack, token *string, user *string) error { osif_sheep.Baa(2, "validating admin token") exp, err := admin.Token_validation(token, user) // ensure token is good and was issued for user if err == nil { osif_sheep.Baa(2, "admin token validated successfully: %s expires: ", *token, exp) } else { osif_sheep.Baa(1, "admin token invalid: %s", err) } return err }
func identity_auth(o *ostack.Ostack, identity_ver *int) (err error) { switch *identity_ver { case 2: err = o.Authorise() case 3: err = o.Authorise_v3() default: fmt.Println("The identity version should be either 2 or 3") os.Exit(1) } return }
/* Crates an array of VM info that can be inserted into the network graph for an entire project. */ func (p *osif_project) Get_all_info(creds *ostack.Ostack, inc_project bool) (ilist []*Net_vm, err error) { err = nil ilist = nil if p == nil || creds == nil { err = fmt.Errorf("creds were nil") osif_sheep.Baa(2, "lazy update: unable to get_all: nil creds") return } if time.Now().Unix()-p.lastfetch > 90 { // if not fresh force a reload first err = p.refresh_maps(creds) osif_sheep.Baa(2, "lazy update: data reload for: get_all") if err != nil { return } } found := 0 ilist = make([]*Net_vm, len(p.ip2vmid)) for k, _ := range p.ip2vmid { name := p.ip2vm[k] _, id, ip4, fip4, mac, gw, phost, gwmap, _, lerr := p.Get_info(&k, creds, true) if lerr == nil { if name == nil { n := "unknown" name = &n } ilist[found] = Mk_netreq_vm(name, id, ip4, nil, phost, mac, gw, fip4, gwmap) found++ } } pname, _ := creds.Get_project() osif_sheep.Baa(1, "get all osvm info found %d VMs in %s", found, *pname) return }
/* Build all translation maps for the given project. Does NOT replace a map with a nil map; we assume this is an openstack glitch. CAUTION: ip2 maps are complete, where vm2 or vmid2 maps are not because they only reference one of the VMs IP addresses where there might be many. */ func (p *osif_project) refresh_maps(creds *ostack.Ostack) (rerr error) { if p == nil { return } if creds == nil { osif_sheep.Baa(1, "IER: refresh_maps given nil creds") rerr = fmt.Errorf("creds were nil") return } if *p.name != "_ref_" { // we don't fetch maps from the ref since it's not real olastfetch := p.lastfetch // last fetch -- ensure it wasn't fetched while we waited p.rwlock.Lock() // wait for a write lock defer p.rwlock.Unlock() // ensure unlocked on return if olastfetch != p.lastfetch { // assume read done while we waited return } osif_sheep.Baa(2, "refresh: creating VM maps from: %s", creds.To_str()) vmid2ip, ip2vmid, vm2ip, vmid2host, vmip2vm, err := creds.Mk_vm_maps(nil, nil, nil, nil, nil, true) if err != nil { osif_sheep.Baa(2, "WRN: unable to map VM info (vm): %s; %s [TGUOSI003]", creds.To_str(), err) rerr = err creds.Expire() // force re-auth next go round } else { osif_sheep.Baa(2, "%s map sizes: vmid2ip=%d ip2vmid=%d vm2ip=%d vmid2host=%d vmip2vm=%d", *p.name, len(vmid2ip), len(ip2vmid), len(vm2ip), len(vmid2host), len(vmip2vm)) if len(vmip2vm) > 0 && len(vmid2ip) > 0 && len(ip2vmid) > 0 && len(vm2ip) > 0 && len(vmid2host) > 0 { // don't refresh unless all are good p.vmid2ip = vmid2ip // id and vm name map to just ONE ip address p.vm2ip = vm2ip p.ip2vmid = ip2vmid // the only complete list of ips p.vmid2host = vmid2host // id to physical host p.ip2vm = vmip2vm } } fip2ip, ip2fip, err := creds.Mk_fip_maps(nil, nil, true) if err != nil { osif_sheep.Baa(2, "WRN: unable to map VM info (fip): %s; %s [TGUOSI004]", creds.To_str(), err) rerr = err creds.Expire() // force re-auth next go round } else { osif_sheep.Baa(2, "%s map sizes: ip2fip=%d fip2ip=%d", *p.name, len(ip2fip), len(fip2ip)) if len(ip2fip) > 0 && len(fip2ip) > 0 { p.ip2fip = ip2fip p.ip2fip = fip2ip } } ip2mac, _, err := creds.Mk_mac_maps(nil, nil, true) if err != nil { osif_sheep.Baa(2, "WRN: unable to map MAC info: %s; %s [TGUOSI005]", creds.To_str(), err) rerr = err creds.Expire() // force re-auth next go round } else { osif_sheep.Baa(2, "%s map sizes: ip2mac=%d", *p.name, len(ip2mac)) if len(ip2mac) > 0 { p.ip2mac = ip2mac } } gwmap, _, gwmac2id, _, _, gwip2phost, err := creds.Mk_gwmaps(nil, nil, nil, nil, nil, nil, true, false) // gwmap is mac2ip if err != nil { osif_sheep.Baa(2, "WRN: unable to map gateway info: %s; %s [TGUOSI006]", creds.To_str(), err) creds.Expire() // force re-auth next go round } else { osif_sheep.Baa(2, "%s map sizes: gwmap=%d", *p.name, len(gwmap)) if len(gwmap) > 0 { p.gwmap = gwmap } for mac, id := range gwmac2id { // run the gateway info and insert as though they were first class VMs ip := gwmap[mac] p.vmid2ip[*id] = ip p.ip2vmid[*ip] = id p.vmid2host[*id] = gwip2phost[*ip] p.vm2ip[*ip] = ip // gw is nameless, so use the ip address } } _, gw2cidr, err := creds.Mk_snlists() // get list of gateways and their subnet cidr if err == nil && gw2cidr != nil { p.gw2cidr = gw2cidr } else { if err != nil { osif_sheep.Baa(1, "WRN: unable to create gateway to cidr map: %s; %s [TGUOSI007]", creds.To_str(), err) } else { osif_sheep.Baa(1, "WRN: unable to create gateway to cidr map: %s no reason given [TGUOSI007]", creds.To_str()) } creds.Expire() // force re-auth next go round } p.lastfetch = time.Now().Unix() } return }
/* executed as a goroutine this loops waiting for messages from the tickler and takes action based on what is needed. */ func Osif_mgr(my_chan chan *ipc.Chmsg) { var ( msg *ipc.Chmsg os_list string = "" os_sects []string // sections in the config file os_refs map[string]*ostack.Ostack // creds for each project we need to request info from os_projects map[string]*osif_project // list of project info (maps) os_admin *ostack.Ostack // admin creds refresh_delay int = 15 // config file can override id2pname map[string]*string // project id/name translation maps pname2id map[string]*string req_token bool = false // if set to true in config file the token _must_ be present when called to validate def_passwd *string // defaults and what we assume are the admin creds def_usr *string def_url *string def_project *string def_region *string ) osif_sheep = bleater.Mk_bleater(0, os.Stderr) // allocate our bleater and attach it to the master osif_sheep.Set_prefix("osif_mgr") tegu_sheep.Add_child(osif_sheep) // we become a child so that if the master vol is adjusted we'll react too //ostack.Set_debugging( 0 ); // ---- pick up configuration file things of interest -------------------------- if cfg_data["osif"] != nil { // cannot imagine that this section is missing, but don't fail if it is def_passwd = cfg_data["osif"]["passwd"] // defaults applied if non-section given in list, or info omitted from the section def_usr = cfg_data["osif"]["usr"] def_url = cfg_data["osif"]["url"] def_project = cfg_data["osif"]["project"] p := cfg_data["osif"]["refresh"] if p != nil { refresh_delay = clike.Atoi(*p) if refresh_delay < 15 { osif_sheep.Baa(1, "resresh was too small (%ds), setting to 15", refresh_delay) refresh_delay = 15 } } p = cfg_data["osif"]["debug"] if p != nil { v := clike.Atoi(*p) if v > -5 { ostack.Set_debugging(v) } } p = cfg_data["osif"]["region"] if p != nil { def_region = p } p = cfg_data["osif"]["ostack_list"] // preferred placement in osif section if p == nil { p = cfg_data["default"]["ostack_list"] // originally in default, so backwards compatable } if p != nil { os_list = *p } p = cfg_data["osif"]["require_token"] if p != nil && *p == "true" { req_token = true } p = cfg_data["osif"]["verbose"] if p != nil { osif_sheep.Set_level(uint(clike.Atoi(*p))) } } if os_list == " " || os_list == "" || os_list == "off" { osif_sheep.Baa(0, "osif disabled: no openstack list (ostack_list) defined in configuration file or setting is 'off'") } else { // TODO -- investigate getting id2pname maps from each specific set of creds defined if an overarching admin name is not given os_admin = get_admin_creds(def_url, def_usr, def_passwd, def_project, def_region) // this will block until we authenticate if os_admin != nil { osif_sheep.Baa(1, "admin creds generated, mapping tenants") pname2id, id2pname, _ = os_admin.Map_tenants() // list only projects we belong to for k, v := range pname2id { osif_sheep.Baa(1, "project known: %s %s", k, *v) // useful to see in log what projects we can see } } else { id2pname = make(map[string]*string) // empty maps and we'll never generate a translation from project name to tenant ID since there are no default admin creds pname2id = make(map[string]*string) if def_project != nil { osif_sheep.Baa(0, "WRN: unable to use admin information (%s, proj=%s, reg=%s) to authorise with openstack [TGUOSI009]", def_usr, def_project, def_region) } else { osif_sheep.Baa(0, "WRN: unable to use admin information (%s, proj=no-project, reg=%s) to authorise with openstack [TGUOSI009]", def_usr, def_region) // YES msg ids are duplicated here } } if os_list == "all" { os_refs, _ = refresh_creds(os_admin, os_refs, id2pname) // for each project in id2pname get current ostack struct (auth) for k := range os_refs { osif_sheep.Baa(1, "inital os_list member: %s", k) } } else { if strings.Index(os_list, ",") > 0 { os_sects = strings.Split(os_list, ",") } else { os_sects = strings.Split(os_list, " ") } os_refs = make(map[string]*ostack.Ostack, len(os_sects)*2) // length is a guideline, not a hard value for i := 0; i < len(os_sects); i++ { osif_sheep.Baa(1, "creating openstack interface for %s", os_sects[i]) url := def_url usr := def_usr passwd := def_passwd project := &os_sects[i] if cfg_data[os_sects[i]] != nil { // section name supplied, override defaults with information from the section if cfg_data[os_sects[i]]["url"] != nil { url = cfg_data[os_sects[i]]["url"] } if cfg_data[os_sects[i]]["usr"] != nil { usr = cfg_data[os_sects[i]]["usr"] } if cfg_data[os_sects[i]]["passwd"] != nil { passwd = cfg_data[os_sects[i]]["passwd"] } if cfg_data[os_sects[i]]["project"] != nil { project = cfg_data[os_sects[i]]["project"] } } os_refs[*project] = ostack.Mk_ostack(url, usr, passwd, project) os_refs["_ref_"] = os_refs[*project] // a quick access reference when any one will do } } os_projects = make(map[string]*osif_project) add2projects(os_projects, os_refs, pname2id, 0) // add refernces to the projects list } // ---------------- end config parsing ---------------------------------------- if os_admin != nil { // only if we are using openstack as a database //tklr.Add_spot( 3, my_chan, REQ_GENCREDS, nil, 1 ) // add tickle spot to drive us once in 3s and then another to drive us based on config refresh rate tklr.Add_spot(int64(180), my_chan, REQ_GENCREDS, nil, ipc.FOREVER) } osif_sheep.Baa(2, "osif manager is running %x", my_chan) for { msg = <-my_chan // wait for next message from tickler msg.State = nil // default to all OK osif_sheep.Baa(3, "processing request: %d", msg.Msg_type) switch msg.Msg_type { case REQ_GENMAPS: // driven by tickler // deprecated with switch to lazy update case REQ_GENCREDS: // driven by tickler now and then if os_admin != nil { os_refs, pname2id, id2pname = update_project(os_admin, os_refs, os_projects, pname2id, id2pname, os_list == "all") } /* ---- before lite ---- case REQ_VM2IP: // driven by tickler; gen a new vm translation map and push to net mgr m := mapvm2ip( os_refs ) if m != nil { count := 0; msg := ipc.Mk_chmsg( ) msg.Send_req( nw_ch, nil, REQ_VM2IP, m, nil ) // send new map to network as it is managed there osif_sheep.Baa( 2, "VM2IP mapping updated from openstack" ) for k, v := range m { osif_sheep.Baa( 3, "VM mapped: %s ==> %s", k, *v ) count++; } osif_sheep.Baa( 2, "mapped %d VM names/IDs from openstack (verbose 3 for debug list)", count ) } */ case REQ_IP2MACMAP: // generate an ip to mac map and send to those who need it (fq_mgr at this point) freq := ipc.Mk_chmsg() // need a new request to pass to fq_mgr data, err := get_ip2mac(os_projects) if err == nil { osif_sheep.Baa(2, "sending ip2mac map to fq_mgr") freq.Send_req(fq_ch, nil, REQ_IP2MACMAP, data, nil) // request data forward msg.State = nil // response ok back to requestor } else { msg.State = err // error goes back to requesting process } case REQ_CHOSTLIST: if msg.Response_ch != nil { // no sense going off to ostack if no place to send the list osif_sheep.Baa(2, "starting list host") msg.Response_data, msg.State = get_hosts(os_refs) osif_sheep.Baa(2, "finishing list host") } else { osif_sheep.Baa(0, "WRN: no response channel for host list request [TGUOSI012]") } /* ======= don't think these are needed but holding ====== case REQ_PROJNAME2ID: // translate a project name (tenant) to ID if msg.Response_ch != nil { pname := msg.Req_data.( *string ) if s, ok := pname2id[*pname]; ok { // translate if there, else assume it's in it's "final" form msg.Response_data = s } else { msg.Response_data = pname } } */ case REQ_VALIDATE_TOKEN: // given token/tenant validate it and translate tenant name to ID if given; returns just ID if msg.Response_ch != nil { s := msg.Req_data.(*string) *s += "/" // add trailing slant to simulate "data" if !have_project(s, pname2id, id2pname) { // ensure that we have creds for this project, if not attempt to get os_refs, pname2id, id2pname = update_project(os_admin, os_refs, os_projects, pname2id, id2pname, os_list == "all") } msg.Response_data, msg.State = validate_token(s, os_refs, pname2id, req_token) } case REQ_GET_HOSTINFO: // dig out all of the bits of host info for a single host from openstack and return in a network update struct if msg.Response_ch != nil { go get_os_hostinfo(msg, os_refs, os_projects, id2pname, pname2id) // do it asynch and return the result on the message channel msg = nil // prevent early response } case REQ_GET_PROJ_HOSTS: if msg.Response_ch != nil { go get_all_osvm_info(msg, os_refs, os_projects, id2pname, pname2id) // do it asynch and return the result on the message channel msg = nil // prevent response from this function } case REQ_GET_DEFGW: // dig out the default gateway for a project if msg.Response_ch != nil { go get_os_defgw(msg, os_refs, os_projects, id2pname, pname2id) // do it asynch and return the result on the message channel msg = nil // prevent early response } case REQ_VALIDATE_HOST: // validate and translate a [token/]project-name/host string if msg.Response_ch != nil { if !have_project(msg.Req_data.(*string), pname2id, id2pname) { // ensure that we have creds for this project, if not attempt to get os_refs, pname2id, id2pname = update_project(os_admin, os_refs, os_projects, pname2id, id2pname, os_list == "all") } msg.Response_data, msg.State = validate_token(msg.Req_data.(*string), os_refs, pname2id, req_token) } case REQ_XLATE_HOST: // accepts a [token/][project/]host name and translate project to an ID if msg.Response_ch != nil { if !have_project(msg.Req_data.(*string), pname2id, id2pname) { // ensure that we have creds for this project, if not attempt to get os_refs, pname2id, id2pname = update_project(os_admin, os_refs, os_projects, pname2id, id2pname, os_list == "all") } msg.Response_data, msg.State = validate_token(msg.Req_data.(*string), os_refs, pname2id, false) // same process as validation but token not required } case REQ_VALIDATE_TEGU_ADMIN: // validate that the token is for the tegu user if msg.Response_ch != nil { if !have_project(msg.Req_data.(*string), pname2id, id2pname) { // ensure that we have creds for this project, if not attempt to get os_refs, pname2id, id2pname = update_project(os_admin, os_refs, os_projects, pname2id, id2pname, os_list == "all") } msg.State = validate_admin_token(os_admin, msg.Req_data.(*string), def_usr) msg.Response_data = "" } case REQ_HAS_ANY_ROLE: // given a token and list of roles, returns true if any role listed is listed by openstack for the token if msg.Response_ch != nil { d := msg.Req_data.(*string) dtoks := strings.Split(*d, " ") // data assumed to be token <space> role[,role...] if len(dtoks) > 1 { msg.Response_data, msg.State = has_any_role(os_refs, os_admin, &dtoks[0], &dtoks[1]) } else { msg.State = fmt.Errorf("has_any_role: bad input data") msg.Response_data = false } } case REQ_PNAME2ID: // user, project, tenant (what ever) name to ID if msg.Response_ch != nil { msg.Response_data = pname2id[*(msg.Req_data.(*string))] if msg.Response_data.(*string) == nil { // maybe it was an ID that came in if id2pname[*(msg.Req_data.(*string))] != nil { // if in id map, then return the stirng (the id) they passed (#202) msg.Response_data = msg.Req_data.(*string) } else { msg.Response_data = nil // couldn't translate } } } default: osif_sheep.Baa(1, "unknown request: %d", msg.Msg_type) msg.Response_data = nil if msg.Response_ch != nil { msg.State = fmt.Errorf("osif: unknown request (%d)", msg.Msg_type) } } if msg != nil { // if msg wasn't passed off to a go routine osif_sheep.Baa(3, "processing request complete: %d", msg.Msg_type) if msg.Response_ch != nil { // if a reqponse channel was provided msg.Response_ch <- msg // send our result back to the requestor } } } }
func main() { var ( o2 *ostack.Ostack = nil all_projects map[string]*string // list of all projects from keystone needed by several tests project_map map[string]*string // list of projects we belong to pwd *string usr *string url *string ) fmt.Fprintf(os.Stderr, "api debugger: v1.11/19235\n") err_count := 0 { p := os.Getenv("OS_USERNAME") usr = &p } // defaults from environment (NOT project!) { p := os.Getenv("OS_AUTH_URL") url = &p } { p := os.Getenv("OS_PASSWORD") pwd = &p } // tests are -<capital> except -P chost_only := flag.Bool("c", false, "list only compute hosts") dump_stuff := flag.Bool("d", false, "dump stuff") host2find := flag.String("h", "", "host search (if -L)") inc_project := flag.Bool("i", false, "include project in names") show_latency := flag.Bool("l", false, "show latency on openstack calls") pwd = flag.String("p", *pwd, "password") project := flag.String("P", "", "project for subsequent tests") region := flag.String("r", "", "region") token := flag.String("t", "", "token") usr = flag.String("u", *usr, "user-name") url = flag.String("U", *url, "auth-url") verbose := flag.Bool("v", false, "verbose") target_vm := flag.String("vm", "", "target VM ID") run_all := flag.Bool("A", false, "run all tests") // the various tests run_crack := flag.Bool("C", false, "crack a token") run_endpt := flag.Bool("E", false, "test endpoint list gen") run_fip := flag.Bool("F", false, "run fixed-ip test") run_gw_map := flag.Bool("G", false, "run gw list test") run_mac := flag.Bool("H", false, "run mac-ip map test") run_info := flag.Bool("I", false, "run vm info map test") run_if := flag.Bool("IF", false, "run get interfaces test") run_hlist := flag.Bool("L", false, "run list-host test") run_maps := flag.Bool("M", false, "run maps test") run_netinfo := flag.Bool("N", false, "run netinfo maps") run_user := flag.Bool("R", false, "run user/role test") run_subnet := flag.Bool("S", false, "run subnet map test") run_vfp := flag.Bool("V", false, "run token valid for project test") run_projects := flag.Bool("T", false, "run projects test") flag.Parse() // actually parse the commandline if *token == "" { token = nil } if *dump_stuff { ostack.Set_debugging(-100) // resets debugging counts to 0 } if *show_latency { ostack.Set_latency_debugging(true) } if url == nil || usr == nil || pwd == nil { fmt.Fprintf(os.Stderr, "usage: debug_ostack_api -U URL -u user -p password [-d] [-i] [-v] [-A] [-F] [-L] [-M] [-T] [-V]\n") fmt.Fprintf(os.Stderr, "usage: debug_ostack_api --help\n") os.Exit(1) } o := ostack.Mk_ostack_region(url, usr, pwd, nil, region) if o == nil { fmt.Fprintf(os.Stderr, "[FAIL] aborting: unable to make ostack structure\n") os.Exit(1) } fmt.Fprintf(os.Stderr, "[OK] created openstack interface structure for: %s %s\n", *usr, *url) region_str := "default" if *region == "" { region = nil } else { region_str = *region } err := o.Authorise() // generic auth without region since we don't give a project on the default creds if err != nil { fmt.Fprintf(os.Stderr, "[FAIL] aborting: authorisation failed: region=%s: %s\n", region_str, err) os.Exit(1) } fmt.Fprintf(os.Stderr, "\n[OK] authorisation for %s (default creds) successful admin flag: %v\n", *usr, o.Isadmin()) if *project == "" || *run_all || *run_projects { // map projects that the user belongs to m1, _, err := o.Map_tenants() if err != nil { fmt.Fprintf(os.Stderr, "[FAIL] aborting: unable to generate a project list (required -A or -T tests): %s\n", err) fmt.Fprintf(os.Stderr, "Is %s an admin?\n", *usr) os.Exit(1) } project_map = m1 if *run_projects { // only announce if specific project test is on if *verbose { fmt.Fprintf(os.Stderr, "\n[OK] project list generation ok:\n") } else { fmt.Fprintf(os.Stderr, "\n[OK] project list map contains %d entries\n", len(m1)) } } for k, v := range m1 { if *project == "" { // save first one for use later if user didn't set cmdline flag project = &k } if !*run_projects { // only list them if specific test is on break } fmt.Fprintf(os.Stderr, "\t\tproject: %s --> %s\n", k, *v) } } if *project != "" { fmt.Fprintf(os.Stderr, "[OK] getting project creds for remaining tests: %s\n", *project) o2 = ostack.Mk_ostack_region(url, usr, pwd, project, region) // project specific creds if o2 == nil { fmt.Fprintf(os.Stderr, "\n[FAIL] unable to alloc creds for specific project; %s\n", *project) os.Exit(1) } err = o2.Authorise() if err != nil { fmt.Fprintf(os.Stderr, "\n[FAIL] unable to authorise creds for specific project; %s\n", *project) os.Exit(1) } } else { fmt.Fprintf(os.Stderr, "[FAIL] did not capture a project name and -P not supplied on command line; cannot attempt any other tests\n") os.Exit(1) } if *run_all || *run_projects { fmt.Fprintf(os.Stderr, "[INFO] sussing host list for each project....\n") for k, _ := range project_map { var hlist *string o3 := ostack.Mk_ostack_region(url, usr, pwd, &k, region) o3.Insert_token(o2.Get_token()) startt := time.Now().Unix() fetch_type := "compute & network" if *chost_only { hlist, err = o3.List_hosts(ostack.COMPUTE) fetch_type = "compute only" } else { hlist, err = o3.List_hosts(ostack.COMPUTE | ostack.NETWORK) } endt := time.Now().Unix() if err == nil { fmt.Fprintf(os.Stderr, "[OK] got hosts (%s) for %s: %s (%d sec)\n", k, fetch_type, *hlist, endt-startt) } else { fmt.Fprintf(os.Stderr, "[WARN] unable to get hosts (%s) for %s: %s (%d sec)", fetch_type, k, err, endt-startt) } } } if *run_projects || *run_all || *run_user { // needed later for both projects and user so get here first all_projects, _, err = o2.Map_all_tenants() // map all tenants using keystsone rather than compute service if err != nil { fmt.Fprintf(os.Stderr, "[FAIL] unable to generate a complete tennt list from keystone: %s\n", err) all_projects = nil } } if all_projects != nil && (*run_all || *run_projects) { // see if we can get a full list of projects fmt.Fprintf(os.Stderr, "[OK] ALL project map contains %d entries:\n", len(all_projects)) if *verbose || !*run_all { for k, v := range all_projects { fmt.Fprintf(os.Stderr, "\t\tproject: %s --> %s\n", k, *v) } if *verbose { fmt.Fprintf(os.Stderr, "[INFO] sussing host list for all known project....\n") for k, _ := range all_projects { o3 := ostack.Mk_ostack_region(url, usr, pwd, &k, region) o3.Insert_token(o2.Get_token()) startt := time.Now().Unix() hlist, err := o3.List_hosts(ostack.COMPUTE) endt := time.Now().Unix() if err == nil { fmt.Fprintf(os.Stderr, "[OK] got compute hosts for %s: %s (%d sec)\n", k, *hlist, endt-startt) } else { fmt.Fprintf(os.Stderr, "[WARN] unable to get compute hosts for %s: %s", k, err) } startt = time.Now().Unix() hlist, err = o3.List_hosts(ostack.NETWORK) endt = time.Now().Unix() if err == nil { fmt.Fprintf(os.Stderr, "[OK] got network hosts for %s: %s (%d sec)\n", k, *hlist, endt-startt) } else { fmt.Fprintf(os.Stderr, "[WARN] unable to get network hosts for %s: %s", k, err) } } } else { fmt.Fprintf(os.Stderr, "[SKIP] did not suss host list for all known project (use -T -v to do this)\n") } } } if *run_all || *run_user { rm, err := o2.Map_roles() // map all roles if err == nil { fmt.Fprintf(os.Stderr, "[OK] Roles found: %d\n", len(rm)) for k, v := range rm { fmt.Fprintf(os.Stderr, "\trole: %s = %s\n", k, *v) } } else { fmt.Fprintf(os.Stderr, "[FAIL] unable to generate a role map from keystone: %s\n", err) err_count++ } /* invoking groles when it's not supported causes the user roles request to fail with an auth failure. don't know if openstack is invalidating the token, or what, but it works when global roles isn't invoked. bloody openstack. rm, err = o2.Map_user_groles() // map global roles for the user if err == nil { fmt.Fprintf( os.Stderr, "[OK] Global roles found: %d\n", len( rm ) ) for k, v := range( rm ) { fmt.Fprintf( os.Stderr, "\trole: %s = %s\n", k, *v ); } } else { fmt.Fprintf( os.Stderr, "[FAIL] unable to generate a global role map from keystone: %s\n", err ) err_count++ } */ rm, err = o2.Map_user_roles(nil) // map the user's roles for the project in the object if err == nil { fmt.Fprintf(os.Stderr, "[OK] Project Roles for the user: %d\n", len(rm)) for k, v := range rm { fmt.Fprintf(os.Stderr, "\trole: %s = %s\n", k, *v) } } else { fmt.Fprintf(os.Stderr, "[FAIL] unable to generate a role map from keystone: %s\n", err) err_count++ } if *verbose { for p, pid := range all_projects { if pid == nil { fmt.Fprintf(os.Stderr, "pid is nil for %s\n", p) } else { rm, err := o2.Map_user_roles(pid) if err != nil { fmt.Fprintf(os.Stderr, "\t%s seems not to be a member of %s\n", *usr, p) } else { fmt.Fprintf(os.Stderr, "\t%s has %d roles in %s\n", *usr, len(rm), p) } } } } } if *run_all || *run_hlist { startt := time.Now().Unix() hlist, err := o2.List_enabled_hosts(ostack.COMPUTE) endt := time.Now().Unix() if err != nil { fmt.Fprintf(os.Stderr, "[FAIL] error generating enabled compute host list: %s\n", err) err_count++ } else { fmt.Fprintf(os.Stderr, "[OK] enabled compute host list: %s (%d sec)\n", *hlist, endt-startt) find_in_list(hlist, host2find) } startt = time.Now().Unix() hlist, err = o2.List_hosts(ostack.COMPUTE) endt = time.Now().Unix() if err != nil { fmt.Fprintf(os.Stderr, "[FAIL] error generating compute host list: %s\n", err) err_count++ } else { fmt.Fprintf(os.Stderr, "[OK] compute host list: %s (%d sec)\n", *hlist, endt-startt) find_in_list(hlist, host2find) } startt = time.Now().Unix() hlist, err = o2.List_hosts(ostack.NETWORK) endt = time.Now().Unix() if err != nil { fmt.Fprintf(os.Stderr, "[FAIL] error generating network host list: %s\n", err) err_count++ } else { fmt.Fprintf(os.Stderr, "[OK] network host list: %s (%d sec)\n", *hlist, endt-startt) find_in_list(hlist, host2find) } startt = time.Now().Unix() hlist, err = o2.List_hosts(ostack.COMPUTE | ostack.NETWORK) endt = time.Now().Unix() if err != nil { fmt.Fprintf(os.Stderr, "[FAIL] error generating combined compute and network host list: %s\n", err) err_count++ } else { fmt.Fprintf(os.Stderr, "[OK] comp & net host list: %s (%d sec)\n", *hlist, endt-startt) find_in_list(hlist, host2find) } startt = time.Now().Unix() hlist, err = o2.List_enabled_hosts(ostack.COMPUTE | ostack.NETWORK) endt = time.Now().Unix() if err != nil { fmt.Fprintf(os.Stderr, "[FAIL] error generating enabled combined compute and network host list: %s\n", err) err_count++ } else { fmt.Fprintf(os.Stderr, "[OK] enabled comp & net host list: %s (%d sec)\n", *hlist, endt-startt) find_in_list(hlist, host2find) } } if *run_all || *run_endpt { eplist, err := o2.Map_endpoints(nil) if err == nil { eplist, err = o2.Map_gw_endpoints(eplist) } if err != nil { fmt.Fprintf(os.Stderr, "[FAIL] unable to generate an enpoint list: %s\n", err) } else { fmt.Fprintf(os.Stderr, "[OK] endpoint list has %d elements\n", len(eplist)) if *verbose { for k, v := range eplist { fmt.Fprintf(os.Stderr, "\tep: %s %s\n", k, v) } } } } if *run_all || *run_if { // interfaces (if) test o2.Get_interfaces(target_vm) } if *run_all || *run_info { m, err := o2.Map_vm_info(nil) if err == nil { fmt.Fprintf(os.Stderr, "[OK] vm info map has %d entries\n", len(m)) if *verbose { for _, v := range m { fmt.Fprintf(os.Stderr, "\tvm_info: %s\n", v) } fmt.Fprintf(os.Stderr, "\n") } } else { fmt.Fprintf(os.Stderr, "[FAIL] error getting vm info map\n") } } if !(*run_all || *run_maps) && *run_gw_map { // just gateway maps (dup below if all maps are generated) // order back: mac2ip, ip2mac, mac2id, id2mac, id2phost gm1, _, gm2, gm3, gm4, gm5, err := o2.Mk_gwmaps(nil, nil, nil, nil, nil, nil, true, *inc_project) if err == nil { gw_id := "" fmt.Fprintf(os.Stderr, "[OK] generated gateway map with %d entries, gw id map with %d entries\n", len(gm1), len(gm2)) if *verbose { fmt.Fprintf(os.Stderr, "\n\tgw mac -> gw-ip\n") for k, v := range gm1 { fmt.Fprintf(os.Stderr, "\tgw1: %s --> %s\n", k, *v) } /* skip ip->mac */ fmt.Fprintf(os.Stderr, "\n\tgw mac -> gw-id\n") for k, v := range gm2 { fmt.Fprintf(os.Stderr, "\tgw2: %s --> %s\n", k, *v) } fmt.Fprintf(os.Stderr, "\n\tgw-id -> mac\n") for k, v := range gm3 { fmt.Fprintf(os.Stderr, "\tgw3: %s --> %s\n", k, *v) } fmt.Fprintf(os.Stderr, "\n\tgw-id --> phost\n") for k, v := range gm4 { gw_id = k extid, _ := o2.Gw2extid(&k) // see if we can look up the external network id if extid != nil { fmt.Fprintf(os.Stderr, "\tgw4: %s --> %s extid=%s\n", k, *v, *extid) } else { fmt.Fprintf(os.Stderr, "\tgw4: %s --> %s\n", k, *v) } } fmt.Fprintf(os.Stderr, "\n\tgw-ip -> phost\n") for k, v := range gm5 { fmt.Fprintf(os.Stderr, "\tgw4: %s --> %s\n", k, *v) } } if gw_id != "" { // just one phost look up to confirm it's working host, err := o2.Gw2phost(&gw_id) // see if we can look up the phost if err == nil { fmt.Fprintf(os.Stderr, "\n[OK] lookup of phys host for gateway ID %s: %s\n", gw_id, *host) } else { fmt.Fprintf(os.Stderr, "\n[FAIL] lookup of phys host for gateway ID %s returned nil: %s\n", gw_id, err) err_count++ } } } else { fmt.Fprintf(os.Stderr, "[FAIL] error generating gateway map: %s\n", err) err_count++ } gl1, err := o2.Mk_gwlist() if err == nil { fmt.Fprintf(os.Stderr, "[OK] generated gateway list %d entries\n", len(gl1)) if *verbose { for i, v := range gl1 { fmt.Fprintf(os.Stderr, "\t gwlist: [%d] %s\n", i, v) } } } else { fmt.Fprintf(os.Stderr, "[FAIL] error generating gateway list: %s\n", err) err_count++ } } if *run_all || *run_netinfo { m, err := o2.Mk_netinfo_map() if err == nil { fmt.Fprintf(os.Stderr, "\n[OK] network info map contains %d entries\n", len(m)) if *verbose { for k, v := range m { fmt.Fprintf(os.Stderr, "\t net_info: %s --> %s\n", k, *v) } } } else { fmt.Fprintf(os.Stderr, "[FAIL] error generating net info map: %s\n", err) err_count++ } } if *run_all || *run_maps { m1, m2, m3, m4, m5, err := o2.Mk_vm_maps(nil, nil, nil, nil, nil, *inc_project) if err != nil { fmt.Fprintf(os.Stderr, "[FAIL] error generating maps: %s\n", err) } else { gm1, _, _, _, gm2, gm3, err := o2.Mk_gwmaps(nil, nil, nil, nil, nil, nil, true, *inc_project) // yes this is a dup, but a shorter output of gw info if err != nil { fmt.Fprintf(os.Stderr, "[FAIL] error generating gw maps: %s\n", err) } else { if m1 == nil { fmt.Fprintf(os.Stderr, "\n[FAIL] unable to alloc map m1 for projet %s\n", *project) os.Exit(1) } if m2 == nil { fmt.Fprintf(os.Stderr, "\n[FAIL] unable to alloc map m2 for projet %s\n", *project) os.Exit(1) } if m3 == nil { fmt.Fprintf(os.Stderr, "\n[FAIL] unable to alloc map m3 for projet %s\n", *project) os.Exit(1) } if m4 == nil { fmt.Fprintf(os.Stderr, "\n[FAIL] unable to alloc map m4 for projet %s\n", *project) os.Exit(1) } if m5 == nil { fmt.Fprintf(os.Stderr, "\n[FAIL] unable to alloc map m5 for projet %s\n", *project) os.Exit(1) } if gm1 == nil || gm2 == nil || gm3 == nil { fmt.Fprintf(os.Stderr, "\n[FAIL] unable to alloc gateway maps for projet %s (%v %v %v)\n", *project, gm2, gm2, gm3) os.Exit(1) } if *verbose { fmt.Fprintf(os.Stderr, "\n[OK] all VM maps were allocated for %s\n", *project) for k, v := range m1 { fmt.Fprintf(os.Stderr, "\tm1: %s --> %s\n", k, *v) } fmt.Fprintf(os.Stderr, "\n") for k, v := range m2 { fmt.Fprintf(os.Stderr, "\tm2: %s --> %s\n", k, *v) } fmt.Fprintf(os.Stderr, "\n") for k, v := range m3 { fmt.Fprintf(os.Stderr, "\tm3: %s --> %s\n", k, *v) } fmt.Fprintf(os.Stderr, "\n") for k, v := range m4 { fmt.Fprintf(os.Stderr, "\tm4: %s --> %s\n", k, *v) } fmt.Fprintf(os.Stderr, "\n") for k, v := range m5 { fmt.Fprintf(os.Stderr, "\tm5: %s --> %s\n", k, *v) } fmt.Fprintf(os.Stderr, "\n") for k, v := range gm1 { fmt.Fprintf(os.Stderr, "\tgw: %s --> %s\n", k, *v) } fmt.Fprintf(os.Stderr, "\n") for k, v := range gm2 { fmt.Fprintf(os.Stderr, "\tgw: %s --> %s\n", k, *v) } fmt.Fprintf(os.Stderr, "\n") for k, v := range gm3 { fmt.Fprintf(os.Stderr, "\tgw: %s --> %s\n", k, *v) } } else { fmt.Fprintf(os.Stderr, "\tm1 contains %d entries\n", len(m1)) fmt.Fprintf(os.Stderr, "\tm2 contains %d entries\n", len(m2)) fmt.Fprintf(os.Stderr, "\tm3 contains %d entries\n", len(m3)) fmt.Fprintf(os.Stderr, "\tm4 contains %d entries\n", len(m4)) fmt.Fprintf(os.Stderr, "\tgw contains %d entries\n", len(gm1)) } } } } if *run_all || *run_mac { mac2tip, err := o2.Mk_mac2tip(nil) if err != nil { fmt.Fprintf(os.Stderr, "[FAIL] error getting mac-info: %s", err) os.Exit(1) } if *verbose { fmt.Fprintf(os.Stderr, "\n[OK] mac2tip address info fetched\n") for mac, ip := range mac2tip { fmt.Fprintf(os.Stderr, "mac2ip: %-15s --> %-15s\n", mac, *ip) } fmt.Fprintf(os.Stderr, "\n") } else { fmt.Fprintf(os.Stderr, "\n[OK] mac2ip map contains %d entries\n", len(mac2tip)) } } if *run_all || *run_fip { ip2fip, fip2ip, err := o2.Mk_fip_maps(nil, nil, *inc_project) if err != nil { fmt.Fprintf(os.Stderr, "[FAIL] error getting fip-info: %s", err) err_count++ } else { if *verbose { fmt.Fprintf(os.Stderr, "\n[OK] floating IP address info fetched\n") for ip, fip := range ip2fip { fmt.Fprintf(os.Stderr, "\tip2fip: %-15s --> %-15s\n", ip, *fip) } fmt.Fprintf(os.Stderr, "\n") for ip, fip := range fip2ip { fmt.Fprintf(os.Stderr, "\tfip2ip: %-15s --> %-15s\n", ip, *fip) } } else { fmt.Fprintf(os.Stderr, "\n[OK] floating IP address info fetched: fip2ip contains %d entries\n", len(fip2ip)) } } } if *run_all || *run_crack { if token != nil { // crack the given token stuff, err := o.Crack_token(token) // generic tegu style call (project is not known) if err != nil { fmt.Fprintf(os.Stderr, "[FAIL] unable to crack the token using unknown project: %s\n", err) err_count++ } else { fmt.Fprintf(os.Stderr, "[OK] token was cracked with unknown project: %s\n", stuff) } stuff, err = o.Crack_ptoken(token, project, true) if err != nil { fmt.Fprintf(os.Stderr, "[FAIL] unable to crack the token using V2: %s\n", err) err_count++ } else { fmt.Fprintf(os.Stderr, "[OK] token was cracked with V2: %s\n", stuff) } stuff, err = o.Crack_ptoken(token, project, false) if err != nil { fmt.Fprintf(os.Stderr, "[FAIL] unable to crack the token using V2: %s\n", err) err_count++ } else { fmt.Fprintf(os.Stderr, "[OK] token was cracked with V2: %s\n", stuff) } } else { fmt.Fprintf(os.Stderr, "[SKIP] did not run crack test, no token provided\n") } } if (*run_all || *run_vfp) && token != nil { // see if token is valid for the given project /* proj, id, err = o.Token2project( token ) if proj != nil { fmt.Fprintf( os.Stderr, "[OK] token maps to project using o1 creds: %s == %s\n", *proj, *id ) } else { fmt.Fprintf( os.Stderr, "\n[FAIL] unable to map token to a project using o1 creds: %s\n", err ) } */ proj, id, err := o2.Token2project(token) if proj == nil { fmt.Fprintf(os.Stderr, "\n[FAIL] unable to map token to a project using o2 creds: %s\n", err) } else { state := o2.Equals_id(id) fmt.Fprintf(os.Stderr, "[OK] token maps to a project using o2 creds: %s == %s pids are equal=%v\n", *proj, *id, state) result, err := o2.Valid_for_project(token, project) if err != nil { fmt.Fprintf(os.Stderr, "\n[FAIL] token NOT valid for project (%s): %s\n", *project, err) if *verbose { fmt.Fprintf(os.Stderr, "\ttoken = %s\n", *token) } err_count++ } else { if result { ptok := *token // keep good messages to something smallish if len(ptok) > 50 { ptok = ptok[0:50] + "...." } fmt.Fprintf(os.Stderr, "[OK] token (%s) valid for project (%s)\n", ptok, *project) } else { err_count++ fmt.Fprintf(os.Stderr, "\n[FAIL] token is NOT valid for project (%s) (no error message)\n", *project) fmt.Fprintf(os.Stderr, "\ttoken = (%s)\n", *token) } } } } else { if *run_vfp && token == nil { fmt.Fprintf(os.Stderr, "\n[INFO] no token supplied with -V option; test skipped\n") } } if *run_all || *run_subnet { msn, mgw, err := o2.Mk_snlists() if err == nil { if msn != nil { fmt.Fprintf(os.Stderr, "\n[OK] subnet map contained %d entries\n", len(msn)) if *verbose { for k, v := range msn { fmt.Fprintf(os.Stderr, "\tsnet: %s = %s\n", k, *v) } } } if mgw != nil { fmt.Fprintf(os.Stderr, "\n[OK] gateway to cidr map contained %d entries\n", len(mgw)) if *verbose { for k, v := range mgw { fmt.Fprintf(os.Stderr, "\tgw2cidr: %s = %s\n", k, *v) } } } } else { if err == nil { fmt.Fprintf(os.Stderr, "\n[FAIL] subnet map was nil\n") } else { fmt.Fprintf(os.Stderr, "\n[FAIL] subnet map generation failed: %s\n", err) } err_count++ } } // ---------------------------------------------------------------------------------------------------- if err_count == 0 { fmt.Fprintf(os.Stderr, "\n[OK] all tests passed\n") } else { fmt.Fprintf(os.Stderr, "\n[WARN] %d errors noticed\n", err_count) } }