package switchy import ( "errors" "fmt" "log" "sort" "strings" "sync" "time" "github.rackspace.com/gorax/clients/dcxSwitchapi" "github.rackspace.com/gorax/clients/sshAPI" "github.rackspace.com/gorax/clients/unipede" "github.rackspace.com/gorax/libs/switchlib/swModels" "github.rackspace.com/gorax/tools/util" ) var ( MAXWORKERS = 5 TIMEOUT = time.Second * 30 ) type ( // generic switch port data, used by dcxAutohost, unipede, ssh direct SwitchClient struct { SwitchName string `json:"switch_name"` DC string `json:"datacenter"` IP string `json:"ip_address"` Chassis string `json:"chassis_model"` OSVersion string `json:"os_version"` Role string `json:"role"` Service string `json:"service"` SwitchPorts []*SwitchPort `json:"switch_ports"` Vlans []*SwitchVlan `json:"vlans"` Neighbors []*SwitchNeighbor `json:"neighbors"` Monitors []*swModels.MonitorSession `json:"monitor_sessions"` Errors []error `json:"-"` portMap map[int]*SwitchPort swapi *dcxSwitchapi.Client token *string u *unipede.Client valid bool ic *swModels.InterfaceConfig vlans *swModels.VlanDB vlanInUse []int intMerge *unipede.UpInterfacesMerge sshClient *sshAPI.SwitchClient } ) // function to initialize a new switch and add port names func NewSwitch(swName, token string, swapi *dcxSwitchapi.Client, u *unipede.Client) (*SwitchClient, error) { return newSwitch(&swName, &token, swapi, u) } func (s *SwitchClient) InitSSHClient() error { var err error if s.IP == "" || s.DC == "" { if err = s.SwitchModelInfo(); err != nil { return err } } if s.sshClient, err = sshAPI.NewSwitchClient(s.IP, s.SwitchName, s.DC, "netbast"); err != nil { return err } return nil } // reusable method to initialize a switch func newSwitch(swName, token *string, swapi *dcxSwitchapi.Client, u *unipede.Client) (*SwitchClient, error) { if u == nil { return nil, errors.New("Unipede client cannot be nil.") } sw := strings.ToLower(*swName) return &SwitchClient{ SwitchName: sw, swapi: swapi, u: u, token: token, }, nil } func newSwitchAndPorts(swName string, token *string, pl []*SwitchPort, swapi *dcxSwitchapi.Client, u *unipede.Client) *SwitchClient { return &SwitchClient{ SwitchName: swName, SwitchPorts: pl, swapi: swapi, u: u, token: token, } } // function to initialize a list of switches, will return a map of switche names and clients with port names. // Bad switch names will return an error in the errors list, and will be marked invalid and not run through additional functions func NewSwitches(swName []string, token string, swapi *dcxSwitchapi.Client, u *unipede.Client) (map[string]*SwitchClient, error) { if u == nil { return nil, errors.New("Unipede client cannot be nil.") } clients := make(map[string]*SwitchClient) for _, sw := range swName { sw := strings.ToLower(sw) if _, ok := clients[sw]; !ok { clients[sw], _ = newSwitch(&sw, &token, swapi, u) } } MapPortNameGather(clients) return clients, nil } // Pulls the basic port info from unipede, will spawn go routines for each switche. Uses MAXWORKERS to define the total number of workers. func MapPortNameGather(clients map[string]*SwitchClient) { swChan := make(chan *SwitchClient) wg := new(sync.WaitGroup) workerStart(swChan, func(sw *SwitchClient) { sw.PortNameGather() }, wg) for _, sw := range clients { wg.Add(1) swChan <- sw } wg.Wait() close(swChan) } // Function to gather port names and base data from unipede, will add all ports if swithports length is 0, otherwise it will only add the ports in the list func (s *SwitchClient) PortNameGather() error { var err error if s.u == nil { return errors.New("Unipede Client Missing.") } s.intMerge, err = s.u.UpInterfacesMerge(s.SwitchName, nil) if err != nil { s.Errors = append(s.Errors, err) return err } s.AddPortNames() s.SortPorts() s.valid = true return nil } func (s *SwitchClient) SwitchModelInfo() error { var err error if s.u == nil { return errors.New("Unipede Client Missing.") } sw, err := s.u.SwitchData(s.SwitchName) if err != nil { s.Errors = append(s.Errors, err) return err } s.Chassis = sw.Chassis s.IP = sw.IPString() s.DC = sw.DC s.OSVersion = sw.OSVersion s.Role = sw.Role s.Service = sw.Service return nil } func MapPortModelInfoGather(clients map[string]*SwitchClient) { swChan := make(chan *SwitchClient) wg := new(sync.WaitGroup) workerStart(swChan, func(sw *SwitchClient) { if err := sw.SwitchModelInfo(); err != nil { log.Println(err) } }, wg) for _, sw := range clients { wg.Add(1) swChan <- sw } wg.Wait() close(swChan) } // method to map port names, if port list is not empty it will add all the ports func (s *SwitchClient) AddPortNames() { if len(s.SwitchPorts) != 0 { s.AddPortsInUse() return } for _, p := range s.intMerge.UpInterfaceMerge { if p == nil { continue } s.SwitchPorts = append(s.SwitchPorts, &SwitchPort{ PortName: p.Name, Port: p.FePort, FeInt: p.FeInt, FeAggTo: p.FeAggTo, CoreDevice: p.CoreDevice, CoreAccount: p.CoreAccount, macmap: make(map[int][]*swModels.MACEntry), }) } } // method to iterate through the port list and only add the items in the list. func (s *SwitchClient) AddPortsInUse() { for _, sp := range s.SwitchPorts { switch { case sp == nil: continue case s.MergePortByNum(sp): continue case s.MergePortByName(sp): continue } } } func (s *SwitchClient) MergePortByNum(port *SwitchPort) bool { if port.Port == 0 { return false } p := s.intMerge.PortByNum(port.Port) if p == nil { return false } port.PortName = p.Name port.FeInt = p.FeInt port.FeAggTo = p.FeAggTo if port.CoreDevice == nil { port.CoreDevice = p.CoreDevice } if port.CoreAccount == nil { port.CoreAccount = p.CoreAccount } return true } func (s *SwitchClient) MergePortByName(port *SwitchPort) bool { if port.PortName == "" { return false } p := s.intMerge.PortByName(port.PortName) if p == nil { return false } port.PortName = p.Name port.FeInt = p.FeInt port.FeAggTo = p.FeAggTo if port.CoreDevice == nil { port.CoreDevice = p.CoreDevice } if port.CoreAccount == nil { port.CoreAccount = p.CoreAccount } return true } // Decorator for starting workers func workerStart(swChan chan *SwitchClient, fn func(sw *SwitchClient), wg *sync.WaitGroup) { for worker := 0; worker < MAXWORKERS; worker++ { go funcWorker(swChan, fn, wg) } } // decorator for switch goroutines func funcWorker(swChan chan *SwitchClient, fn func(*SwitchClient), wg *sync.WaitGroup) { for sw := range swChan { fn(sw) wg.Done() } } // method to sort all ports func (s *SwitchClient) SortPorts() { pm := make(map[int]*SwitchPort) for _, sp := range s.SwitchPorts { if sp == nil { continue } pm[sp.FeInt] = sp } var keys []int for port := range pm { keys = append(keys, port) } sort.Ints(keys) var sp []*SwitchPort for _, key := range keys { sp = append(sp, pm[key]) } s.portMap = pm s.SwitchPorts = sp } // method to convert a list of ports to switch clients, for eaier data gather. func PortToSwitchMap(pm map[string][]*SwitchPort, token *string, swapi *dcxSwitchapi.Client, u *unipede.Client) (map[string]*SwitchClient, error) { if u == nil { return nil, errors.New("Unipede client cannot be nil.") } clients := make(map[string]*SwitchClient) for sw, pl := range pm { clients[sw] = newSwitchAndPorts(sw, token, pl, swapi, u) } return clients, nil } func (s *SwitchClient) AllVlans() (pl []int) { for _, p := range s.SwitchPorts { if p.PortConfig != nil { pl = append(pl, p.PortConfig.PortVlans()...) } } sort.Ints(pl) return util.UniqueInts(pl) } func (s *SwitchClient) VlanByPorts(pn []int) (pl []int) { for _, p := range pn { pc := s.PortByNum(p) if pc != nil { if pc.PortConfig != nil { pl = append(pl, pc.PortConfig.PortVlans()...) } } } sort.Ints(pl) return util.UniqueInts(pl) } func (s *SwitchClient) PortByNum(pn int) *SwitchPort { for _, p := range s.SwitchPorts { if p.Port == pn { return p } } return nil } func (s *SwitchClient) MissingVlans(vl []int) (vlans []int) { for _, v := range vl { if !s.VlanInConfig(v) { vlans = append(vlans, v) } } return } func (s *SwitchClient) MissingSwitchVlans(vl []int) (mvl []string) { vlans := s.MissingVlans(vl) for _, v := range vlans { mvl = append(mvl, fmt.Sprintf("Missing Vlan in DB switch:%s, %d", s.SwitchName, v)) // s.Errors = append(s.Errors, fmt.Errorf("Missing Vlan in DB switch:%s, %d", s.SwitchName, v)) //log.Printf("Missing Vlan in DB switch:%s, %d", s.SwitchName, v) } return mvl } func (s *SwitchClient) VlanInConfig(v int) bool { for _, vlan := range s.Vlans { if vlan.Vlan == v { return true } } return false } func (s *SwitchClient) FindVlanConfig(v int) *SwitchVlan { for _, vlan := range s.Vlans { if vlan.Vlan == v { return vlan } } return nil } func (s *SwitchClient) MissingVlanDB(pl []int) { if len(pl) == 0 { pl = s.AllVlans() } else { pl = s.VlanByPorts(pl) } s.MissingVlans(pl) } func (s *SwitchClient) MissingVlanInt(v int) bool { if i := s.ic.PortByName(fmt.Sprintf("vlan%d", v)); i != nil { return false } return true } func (s *SwitchClient) RemoveMgmtVlans(vl []int) (cl []int) { for _, v := range vl { if vlan := s.FindVlanConfig(v); vlan != nil { if !vlan.IsManagement() { cl = append(cl, v) } } else { log.Printf("%s Unable to find vlan: %d", s.SwitchName, v) } } return } func (s *SwitchClient) AggrTrunkMissing(pl []int) (mvl []string) { if len(pl) == 0 { pl = s.AllVlans() } else { pl = s.VlanByPorts(pl) } pl = s.RemoveMgmtVlans(pl) for _, n := range s.Neighbors { if n.Role != "aggr" || n.sw == nil { continue } // for _, p := range n.NeighborPorts { // if p.Remote != nil { // if p.Remote.PortConfig != nil { // // s.NeighborMissing(p.Remote.PortConfig.MissingVlans(pl), n.Name) // } // } // } if !strings.Contains(strings.ToLower(s.Service), "public") { continue } for _, v := range n.sw.MissingVlans(pl) { if n.sw.MissingVlanInt(v) { mvl = append(mvl, fmt.Sprintf("%s Missing Vlan in DB Aggr: %s DB: %d", s.SwitchName, n.sw.SwitchName, v)) // s.Errors = append(s.Errors, fmt.Errorf("%s Missing Vlan in DB Aggr: %s DB: %d", s.SwitchName, n.sw.SwitchName, v)) // log.Printf("%s Missing Vlan in DB Aggr: %s DB: %d", s.SwitchName, n.sw.SwitchName, v) } } } return mvl } func (s *SwitchClient) NeighborMissing(ml []int, neighbor string) { for _, v := range ml { s.Errors = append(s.Errors, fmt.Errorf("Vlan Missing on Aggr: %s, Vlan: %d", neighbor, v)) log.Printf("Vlan Missing on Aggr: %s, Vlan: %d", neighbor, v) } }