// 2017 Rackspace, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. // Package snapshot interfaces with network endpoints to gather a set of devices port macs/arps. package snapshot import ( "fmt" "net/http" "net/url" "strconv" "strings" "time" "github.rackspace.com/DCM/migration/pkg/collection/ebi" "github.rackspace.com/DCM/migration/pkg/collection/network" "github.rackspace.com/DCM/tools" "github.rackspace.com/gorax/asaapi/pkg/asaModels" "github.rackspace.com/gorax/clients/mydp" "github.com/sirupsen/logrus" adx "github.rackspace.com/gorax/adx/lib" "github.rackspace.com/gorax/asaapi/pkg/asa" "github.rackspace.com/gorax/clients/coreAPI" "github.rackspace.com/gorax/clients/unipede" f5 "github.rackspace.com/gorax/f5api/pkg" "github.rackspace.com/gorax/passkey" ) type ( // Client type is the initiating struct for the snapshot package. // swagger:ignore Client struct { u *unipede.Client coreAPI *coreAPI.Client pw *passkey.Client dp *mydp.Client macs map[string][]unipede.MacViewItems } // DBDevice type is a representation of the snapshot data gathered for // a particular DB device. DBDevice struct { ID int `json:"_id" bson:"_id"` AccountNumber int `json:"account_number" bson:"account_number"` AccountName string `json:"account_name" bson:"account_name"` Current struct { VPNs []asaModels.Lan2Lan `json:"vpns" bson:"vpns"` ARPs []arp `json:"arps" bson:"arps"` DeviceInterfaces []deviceInf `json:"device_interfaces" bson:"device_interfaces"` SummaryPorts []string `json:"summary_ports" bson:"summary_ports"` SwitchPorts []switchPort `json:"switch_ports" bson:"switch_ports"` } `json:"current" bson:"current"` Migration struct { VPNs []asaModels.Lan2Lan `json:"vpns" bson:"vpns"` ARPs []arp `json:"arps" bson:"arps"` DeviceInterfaces []deviceInf `json:"device_interfaces" bson:"device_interfaces"` SummaryPorts []string `json:"summary_ports" bson:"summary_ports"` SwitchPorts []switchPort `json:"switch_ports" bson:"switch_ports"` } `json:"migration" bson:"migration"` MoveGroup string `json:"move_group" bson:"move_group"` DeviceStatus string `json:"device_status" bson:"device_status"` DeviceModel string `json:"device_model" bson:"device_model"` } // Device type is a representation of the snapshot data gathered for // a particular DB device. Device struct { ID int `json:"device_number" bson:"device_number"` AccountNumber int `json:"account_number" bson:"account_number"` AccountName string `json:"account_name" bson:"account_name"` MoveGroup string `json:"move_group" bson:"move_group"` DeviceStatus string `json:"device_status" bson:"device_status"` DeviceModel string `json:"device_model" bson:"device_model"` SummaryPorts []string `json:"summary_ports" bson:"summary_ports"` SwitchPorts []switchPort `json:"switch_ports" bson:"switch_ports"` DeviceInterfaces []deviceInf `json:"device_interfaces" bson:"device_interfaces"` ARPs []arp `json:"arps" bson:"arps"` VPNs []asaModels.Lan2Lan `json:"vpns" bson:"vpns"` TimeStamp string `json:"timestamp" bson:"timestamp"` } switchPort struct { Name string `json:"switch_name" bson:"switch_name"` Service string `json:"switch_service" bson:"switch_service"` Interface string `json:"interface" bson:"interface"` Duplex string `json:"duplex" bson:"duplex"` FePort string `json:"fe_port" bson:"fe_port"` Mode string `json:"mode" bson:"mode"` Admin string `json:"admin" bson:"admin"` Link string `json:"link" bson:"link"` State string `json:"state" bson:"state"` Speed string `json:"speed" bson:"speed"` Macs []string `json:"macs" bson:"macs"` } deviceInf struct { Name string `json:"name" bson:"name"` AltName string `json:"alt_name" bson:"alt_name"` Link string `json:"link" bson:"link"` Mac string `json:"mac" bson:"mac"` } arp struct { Address string `json:"address" bson:"address"` IfName string `json:"if_name" bson:"if_name"` Mac string `json:"mac_address" bson:"mac_address"` Expire int `json:"expire,string" bson:"expire,string"` } ) // New function will take the direct references to the clients required for this package. // This is under the assumption that you have initiated your own client and are passing by // reference. func New(u *unipede.Client, coreAPI *coreAPI.Client, mydp *mydp.Client, pw *passkey.Client) *Client { return &Client{ u: u, coreAPI: coreAPI, pw: pw, dp: mydp, macs: make(map[string][]unipede.MacViewItems), } } // NewContext function will take a pointer to a Request, and pull the client references // from the Context() offered within the default net/http package. func NewContext(r *http.Request) *Client { return &Client{ u: unipede.FromContext(r.Context()), coreAPI: coreAPI.FromContext(r.Context()), dp: mydp.FromContext(r.Context()), pw: passkey.FromContext(r.Context()), macs: make(map[string][]unipede.MacViewItems), } } // DeviceSnapshot method will take a unipede client to access // switchport and device data for a given device func (c *Client) DeviceSnapshot(d tools.Device, state string, refresh *int) (dss *Device, err error) { // Set device specific information within this struct dss = &Device{ ID: d.ID, AccountName: d.Account.Name, AccountNumber: d.Account.Number, MoveGroup: d.MoveGroup, DeviceModel: d.Device.ComponentConfigType, DeviceStatus: d.Device.Status, SummaryPorts: d.Environment.Current.SwitchPorts, TimeStamp: time.Now().Format(time.UnixDate), } if err = c.gatherSwitchportMacs(dss, d, state, refresh); err != nil { logrus.Errorf("DeviceSnapshot:gatherSwitchportMacs:%s", err) } if err = c.gatherDevice(dss, d); err != nil { logrus.Errorf("DeviceSnapshot:gatherDevice:%s", err) } return } // gatherSwitchportMacs method will take a unipede client to access // switchport data for each port associated to a device func (c *Client) gatherSwitchportMacs(dss *Device, d tools.Device, state string, refresh *int) (err error) { portParsed := d.Environment.Current.PortsParsed if state == "migration" { portParsed = d.Environment.Migration.PortsParsed } // Iterate through each device switchport, and query Unipede for MAC address // information for _, port := range portParsed { sw := strings.ToLower(fmt.Sprintf("%s.%s", port.SwitchName, port.DataCenter)) // Set the unipede filter for the port filter := []string{"fe_port:=" + strconv.Itoa(port.PortNumber)} if port.PortName != "" { filter = []string{"name:=" + port.PortName} } intView, e := c.u.UpInterfaceDeviceView(url.Values{"filter": filter}, sw, refresh) if e != nil { err = e continue } if len(intView.Items) != 1 { continue } swPort := switchPort{ Name: sw, Link: intView.Items[0].Link, Admin: intView.Items[0].Admin, Mode: intView.Items[0].Mode, Interface: intView.Items[0].Name, State: intView.Items[0].State, Speed: intView.Items[0].Speed, Duplex: intView.Items[0].Duplex, FePort: strconv.Itoa(intView.Items[0].FEPort), Service: port.PortType, Macs: make([]string, 0), } macs, e := c.macsForPort(sw, intView.Items[0].Name) if e != nil { err = e continue } // Iterate through each response and append on MAC addresses for _, mac := range macs { swPort.Macs = append(swPort.Macs, fmt.Sprintf("Vlan%d:%s", mac.Vlan, mac.Mac)) } // Add the port into the switchport list for the device dss.SwitchPorts = append(dss.SwitchPorts, swPort) } return } // gatherDevice method will take a unipede client to access // switchport data for each port associated to a device func (c *Client) gatherDevice(dss *Device, d tools.Device) (err error) { model := strings.ToLower(d.Device.ComponentConfigType) switch { case strings.Contains(model, "cisco asa") || strings.Contains(model, "asa 5505"): if d.Linked.Type == ebi.LinkedByIntegration && d.Linked.ID != 0 { a, err := network.FetchDPASA(c.dp, c.pw, d) if err != nil { return err } if dl, err := gatherASAMacs(a); err == nil { dss.DeviceInterfaces = dl } if arp, err := gatherASAARPs(a); err == nil { dss.ARPs = arp } if vpns, err := gatherVPNs(a); err == nil { dss.VPNs = vpns } } else { a, err := asa.NewASAToken(c.coreAPI, d.ID) if err != nil { return err } if dl, err := gatherASAMacs(a); err == nil { dss.DeviceInterfaces = dl } if arp, err := gatherASAARPs(a); err == nil { dss.ARPs = arp } if vpns, err := gatherVPNs(a); err == nil { dss.VPNs = vpns } } return err case strings.Contains(model, "brocade adx"): a, err := adx.NewADXToken(c.coreAPI, d.ID, c.pw.Service.TacacsLogin.Credential) if err != nil { return err } if dl, err := gatherADXMacs(a); err == nil { dss.DeviceInterfaces = dl } if arp, err := gatherADXARPs(a); err == nil { dss.ARPs = arp } return err case strings.Contains(model, "bigip"): f, err := f5.New(f5.Params{ Core: c.coreAPI, Device: d.ID, Public: false, }) if err != nil { return err } if dl, err := gatherF5Macs(f); err == nil { dss.DeviceInterfaces = dl } if arp, err := gatherF5ARPs(f); err == nil { dss.ARPs = arp } return err } return err } // macsForPort will search the mapping of mac addresses to determine the port that // the mac address lives on. func (c *Client) macsForPort(switchName, ifName string) ([]unipede.MacViewItems, error) { r := make([]unipede.MacViewItems, 0) // Collect the mac-addresses for the entire switch. This should only happen once per // API call. if _, ok := c.macs[switchName]; !ok { macViews, err := c.u.UpMacView(switchName, nil, nil, &unipede.UnipedeOptions{}) if err != nil { return r, fmt.Errorf("snapshot.gatherSwitchportMacs.%s:%s", switchName, err) } c.macs[switchName] = macViews.Items } for _, m := range c.macs[switchName] { // These little guys, I wouldn't worry about these little guys. if m.Mac == "ffff.ffff.ffff" || m.Mac == "3333.0000.000d" { continue } // If the mac-address doesn't have an interface name associated with it, it's not // going to do us any good. if m.UpInterfaceName == "" { continue } // If the mac-address is from a root port or a vPC peer-link, then we don't want it if m.UpDeviceVlanPortRole == "root" || strings.Contains(m.Type, "peer-link") { continue } // If the mac-address port-name matches our port name from the port argument, add it on. if ifName == m.UpInterfaceName { r = append(r, m) } } return r, nil } // gatherASAMacs method will take a unipede client to access // switchport data for each port associated to a device func gatherASAMacs(a *asa.ASA) (di []deviceInf, err error) { if err = a.ShowInterfaces(); err != nil { return } for _, i := range a.Status.Interfaces { link := "down" if i.Link { link = "up" } di = append(di, deviceInf{Name: i.Name, Link: link, AltName: i.IfName, Mac: i.MAC}) } return } // gatherASAARPs method will take an asa type and pull the ARP // entries on the device. func gatherASAARPs(a *asa.ASA) (resp []arp, err error) { if err = a.GetArp(); err != nil { return } for _, r := range a.Status.ARP { resp = append(resp, arp{Address: r.Address, IfName: r.IfName, Mac: r.Mac, Expire: r.TimeOut}) } return } // gatherVPNs method will take an asa type and pull the VPN // entries on the device. func gatherVPNs(a *asa.ASA) (resp []asaModels.Lan2Lan, err error) { l2ls, err := a.VPNSessionL2L() if err != nil { return } for _, l2l := range l2ls { if l2l != nil { resp = append(resp, *l2l) } } return } // gatherADXMacs method will take a unipede client to access // switchport data for each port associated to a device func gatherADXMacs(a *adx.ADX) (di []deviceInf, err error) { if err = a.GetInterfaceStats(); err != nil { return } if err = a.GetVEInterfaces(); err != nil { return } for _, ve := range a.Status.VEInterfaces.Int { di = append(di, deviceInf{Name: ve.Name, Mac: ve.MacAddress, Link: ve.LineProtocol, AltName: ve.Hardware}) } for _, i := range a.Status.Interfaces { di = append(di, deviceInf{Name: i.Name, Mac: i.MacAddress, Link: i.LineProtocol, AltName: i.State}) } return } // gatherADXARPs method will take an adx type and pull the ARP // entries on the device. func gatherADXARPs(a *adx.ADX) (resp []arp, err error) { if err = a.GetARP(); err != nil { return } for _, r := range a.Status.Arp { resp = append(resp, arp{Address: r.IpAddr, IfName: r.Port, Mac: r.MacAddr, Expire: r.Age}) } return } // gatherF5Macs method will take a unipede client to access // switchport data for each port associated to a device func gatherF5Macs(f *f5.F5) (di []deviceInf, err error) { if err = f.InterfaceConfig("", 15); err != nil { return } if err = f.InterfaceStatus(15); err != nil { return } if err = f.VlanStatus("", 15); err != nil { return } for _, i := range f.Status.Vlans { var si f5.IntStats for _, s := range f.Status.Interfaces { if s.Name == i.Member { si = *s break } } di = append(di, deviceInf{Name: i.Name, Link: si.Status, AltName: i.Member, Mac: i.Mac}) } return } // gatherF5ARPs method will take an F5 type and pull the ARP // entries on the device. func gatherF5ARPs(f *f5.F5) (resp []arp, err error) { if err = f.ARPStatus(60); err != nil { return } for _, r := range f.Status.ARP { if strings.ToLower(r.MacAddr) == "incomplete" { continue } resp = append(resp, arp{Address: r.Address, IfName: r.Vlan, Mac: r.MacAddr, Expire: r.Expiration}) } return }