// 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 network import ( "fmt" "strconv" "strings" "github.rackspace.com/DCM/migration/pkg/collection" "github.rackspace.com/DCM/migration/pkg/collection/mongo" "github.rackspace.com/DCM/migration/pkg/tools/journal" "github.rackspace.com/DCM/tools" "github.rackspace.com/gorax/clients/unipede" "github.rackspace.com/gorax/tools/util" "go.mongodb.org/mongo-driver/bson" ) // UpdatePublicZoneData method will spawn a set of go routines to query Unipede and gather the // associated public zone for a given set of devices. func (c *Client) UpdatePublicZoneData(dl []tools.Device) (resp collection.UpdateResponse, err error) { zc := zoneCountMap(dl) chMissed := make(chan []collection.Missed) chInt := make(chan int) chnk := mongo.Chunk(dl, routines) defer close(chMissed) defer close(chInt) r = aggrReplacer(c.db.CollectionDC()) for _, item := range chnk { go runUnipedeRoutine(c, item, zc, chMissed, chInt) } for range chnk { resp.Missed = append(resp.Missed, <-chMissed...) resp.Updated += <-chInt } return } // runUnipedeRoutine is an internal function to kick off the UpdatePublicZoneData routines. func runUnipedeRoutine(c *Client, dl []tools.Device, zc map[int][]string, chMissed chan<- []collection.Missed, chInt chan<- int) { var resp collection.UpdateResponse for _, item := range dl { var zoneInfo = PublicZoneInfo{ ZoneCount: len(zc[item.Account.Number]), AccountZones: zc[item.Account.Number], } if msg, ok := zoneLookupEligible(item); !ok { resp.Missed = append(resp.Missed, collection.Missed{Item: item.ID, Details: msg}) if uErr := c.db.UpdateOne( c.Collection, bson.D{{"_id", item.ID}}, bson.D{{"$set", bson.D{{"network.public", zoneInfo}}}}, true, ); uErr != nil { resp.Missed = append(resp.Missed, collection.Missed{Item: item.ID, Details: uErr.Error()}) continue } continue } s, err := c.u.GetStaticRouteByIP(item.Device.PrimaryIP, nil) if err != nil { resp.Missed = append(resp.Missed, collection.Missed{Item: item.ID, Details: err.Error()}) continue } ip := item.Device.PrimaryIP if s.Count != 0 { for _, dev := range s.Data { if dev.NextHopAddress != "" { ip = dev.NextHopAddress break } } } zoneInfo, err = unipedeGather(c, ip) if err != nil { resp.Missed = append(resp.Missed, collection.Missed{Item: item.ID, Details: err.Error()}) continue } zoneInfo.ZoneCount = len(zc[item.Account.Number]) zoneInfo.AccountZones = zc[item.Account.Number] if item.MoveGroup != "" { // Determine if there was a change in the Public Zone from its previous value, if // so, send an alert to NewRelic. if alert, changed := networkPublicChanges(zoneInfo, item); changed { if err := journal.Audits(alert, c.Collection, c.nr); err != nil { resp.Missed = append(resp.Missed, collection.Missed{Item: item.ID, Details: err.Error()}) continue } } } update := bson.D{ {"$set", bson.D{ {"network.public", zoneInfo}, }}, } // gather zone data, as a backup option if DCX does not have switch zone data if c.MigrationType != tools.MigTypeRackspace { ports := item.Environment.Current.PortsParsed for i, p := range ports { neighbors, err := c.u.UpNeighbors(strings.ToLower(fmt.Sprintf("%s.%s", p.SwitchName, p.DataCenter)), nil) if err != nil { resp.Missed = append(resp.Missed, collection.Missed{Item: item.ID, Details: err.Error()}) continue } for _, n := range neighbors.Items { if strings.Contains(strings.ToLower(n.RemoteHostname), "agg") || strings.Contains(strings.ToLower(n.RemoteHostname), "dist") { ports[i].ZoneName = r.Replace(n.RemoteHostname) break } } } update = bson.D{ {"$set", bson.D{ {"network.public", zoneInfo}, {"environment.current.switch_ports_parsed", ports}, }}, } } if uErr := c.db.UpdateOne( c.Collection, bson.D{{"_id", item.ID}}, update, false, ); uErr != nil { resp.Missed = append(resp.Missed, collection.Missed{Item: item.ID, Details: uErr.Error()}) continue } resp.Updated++ } chMissed <- resp.Missed chInt <- resp.Updated } func unipedeGather(c *Client, address string) (zi PublicZoneInfo, err error) { var aggr unipede.UnipedeDeviceModel connected, err := c.u.GetConnectedRouteByIP(address, nil) if err != nil { return } if connected.Count != 0 { for _, v := range connected.Data { aggr, err = c.u.UnipedeDeviceModel(v.UpDeviceID, nil) if err != nil { return } for _, ag := range aggr.Data { // Check to make sure the switch we found is actually located in the correct DC. if strings.Contains(strings.ToLower(ag.Hostname), fmt.Sprintf(".%s", c.db.CollectionDC())) { vlanID := func() int { if vlanID, err := strconv.Atoi(strings.TrimPrefix(strings.ToLower(v.Interface), "vlan")); err == nil { return vlanID } return 0 }() zi = PublicZoneInfo{Aggr: ag.Hostname, Vlan: vlanID, Zone: fmt.Sprintf("%v:%v", r.Replace(ag.Hostname), v.Interface)} return } } } } return } func zoneLookupEligible(item tools.Device) (string, bool) { if item.Device.PrimaryIP == "" || item.Device.PrimaryIP == "0" { return "No IP address found", false } if !util.IsRoutedIP(item.Device.PrimaryIP) { return "Primary IP is not a routable address", false } for _, model := range []string{ "Fibre Switch", "VNX", "EMC RecoverPoint Appliance", "Dedicated SAN"} { if strings.Contains(item.Device.MakeModel, model) { return "Device model does not typically have a routable address", false } } return "", true } func zoneCountMap(devList []tools.Device) map[int][]string { zoneCount := make(map[int][]string) for _, item := range devList { if item.Network.Public.Zone == "" { continue } exist := false if _, ok := zoneCount[item.Account.Number]; !ok { zoneCount[item.Account.Number] = []string{} } for _, zone := range zoneCount[item.Account.Number] { if zone == item.Network.Public.Zone { exist = true } } if !exist { zoneCount[item.Account.Number] = append(zoneCount[item.Account.Number], item.Network.Public.Zone) } } return zoneCount }