// 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 dcx import ( "fmt" "log" "strings" "time" "go.mongodb.org/mongo-driver/bson" "github.rackspace.com/DCM/migration/pkg/collection" "github.rackspace.com/DCM/migration/pkg/collection/mongo" "github.rackspace.com/DCM/migration/pkg/tools" dcmTools "github.rackspace.com/DCM/tools" "github.rackspace.com/gorax/clients/dcxAutohost" ) const wafMakeModel = "Firewall - Web Application" // UpdateIPandEnv method will chunk a list of DB device types, then run a routine to call out to the // DCX API endpoint, gather IP information, and update the Mongo database with the results func (c *Client) UpdateIPandEnv(mdl []dcmTools.Device) (resp collection.UpdateResponse) { chMissed := make(chan []collection.Missed) chInt := make(chan int) chunk := mongo.Chunk(mdl, routines) defer close(chMissed) defer close(chInt) // update IPs and environment info for _, item := range chunk { go runDCXIPRoutine(c, item, chMissed, chInt) } for range chunk { resp.Missed = append(resp.Missed, <-chMissed...) resp.Updated += <-chInt } return } // UpdateVlans method will chunk a list of envs, then run a routine to call out to the // DCX API endpoint or our db, and update the Mongo database with the results func (c *Client) UpdateVlans(envs []string, resp *collection.UpdateResponse) { chMissed := make(chan []collection.Missed) defer close(chMissed) // pull latest vlan translations from either DCX or our DB, depending on migration type envChunk := Chunk(envs, routines) for _, item := range envChunk { go runDCXMigVLANsRoutine(c, item, chMissed) } for range envChunk { resp.Missed = append(resp.Missed, <-chMissed...) } } // runDCXIPRoutine is the internal function to process the chunks from the UpdateDCXIPs method. func runDCXIPRoutine(c *Client, dl []dcmTools.Device, chMissed chan<- []collection.Missed, chInt chan<- int) { var resp collection.UpdateResponse for _, item := range dl { dIP, err := c.dcx.DeviceIPs(item.ID) if err != nil { resp.Missed = append(resp.Missed, collection.Missed{Item: item.ID, Details: fmt.Sprintf("em.DeviceIPs %v", err.Error())}) continue } var ( primaryIP string ) // create with slices to prevent null values in the DB ipStruct := c.newIPStruct(item) if len(dIP.Items) > 0 { // Gather the IPv6 addresses for _, ip := range dIP.Items[0].IPv6IPs { ipStruct.IPv6 = append(ipStruct.IPv6, dcmTools.IPv6AddressDetails{ IPBlockCIDR: ip.IPBlockCIDR, NetworkName: ip.NetworkName, VLANNumber: ip.VLANNumber, Address: ip.Address, Zone: ip.Zone, }) } // Gather the DRAC IP information for _, ip := range dIP.Items[0].PublicIPs { // set the primary IP address variable if ip.Label == "primary" { primaryIP = ip.IPAddress continue } if ip.NetworkType == "DRAC" { ipStruct.setDRAC(ip, c.MigrationType) } if ip.NetworkType == "ServicesNet" { ipStruct.setPublicServiceNet(ip) } } // Gather the BackupNet IP information for _, ip := range dIP.Items[0].PrivateIPs { // set the primary IP address variable if it's not already set if ip.Label == "primary" && primaryIP == "" { primaryIP = ip.IPAddress continue } if ip.NetworkType == "Managed Backup" { ipStruct.setMBU(ip) } if ip.NetworkType == "DRAC" { ipStruct.setPrivDRAC(ip, c.MigrationType) } if ip.NetworkType == "VMNet" { ipStruct.setVMNet(ip) } if ip.NetworkType == "ServicesNet" { ipStruct.setServiceNet(ip) } } } update := bson.D{ {"device.primary_ip", primaryIP}, } if c.RackspaceMigDC { // Retry loop to attempt to get environment manager data var ( environ dcmTools.EnvironmentData ) if err := tools.Retry(3, 2*time.Second, func() error { var err error //nolint: scopelint environ, err = c.collectDCXInfo(item) return err }); err != nil { resp.Missed = append(resp.Missed, collection.Missed{Item: item.ID, Details: fmt.Sprintf("tools.Retry %v", err.Error())}) continue } // gather infrastructure zone data right about hurrr.... for i, x := range environ.Migration.PortsParsed { swName := strings.ToLower(fmt.Sprintf("%s.%s", x.SwitchName, x.DataCenter)) z, err := c.getZoneFromDevice(swName) if err != nil { resp.Missed = append(resp.Missed, collection.Missed{Item: item.ID, Details: fmt.Sprintf("zoneUpdate:%s %v", swName, err.Error())}) continue } environ.Migration.PortsParsed[i].ZoneName = z.ZoneName environ.Migration.PortsParsed[i].ZoneNumber = z.ZoneNumber } if c.MigrationType == dcmTools.MigTypeRackspace { for i, x := range environ.Current.PortsParsed { swName := strings.ToLower(fmt.Sprintf("%s.%s", x.SwitchName, x.DataCenter)) z, err := c.getZoneFromDevice(swName) if err != nil { resp.Missed = append(resp.Missed, collection.Missed{Item: item.ID, Details: fmt.Sprintf("zoneUpdate:%s %v", swName, err.Error())}) continue } environ.Current.PortsParsed[i].ZoneName = z.ZoneName environ.Current.PortsParsed[i].ZoneNumber = z.ZoneNumber } } update = append(update, bson.E{"environment", environ}, bson.E{"network.address", ipStruct}) } uErr := c.db.UpdateOne( c.Collection, bson.D{{"_id", item.ID}}, bson.D{{"$set", update}}, true, ) if uErr != nil { resp.Missed = append(resp.Missed, collection.Missed{Item: item.ID, Details: fmt.Sprintf("mongo.UpdateOne %v", uErr.Error())}) continue } if strings.EqualFold(item.Device.MakeModel, wafMakeModel) && item.Environment.Number != "" { uErr := c.db.UpdateMany( c.Collection, bson.D{{"environment.number", item.Environment.Number}}, bson.D{{"$set", bson.D{{"has_waf", true}}}}, false, nil, ) if uErr != nil { resp.Missed = append(resp.Missed, collection.Missed{Item: item.ID, Details: fmt.Sprintf("mongo.UpdateMany %v", uErr.Error())}) continue } } resp.Updated++ } chMissed <- resp.Missed chInt <- resp.Updated } // runDCXMigVLANsRoutine is an internal function to spawn routines for the UpdateMigrationVLANs method. func runDCXMigVLANsRoutine(c *Client, envs []string, chMissed chan<- []collection.Missed) { var resp collection.UpdateResponse for _, env := range envs { var err error var vlans []dcxAutohost.EnvironmentMigrationVlan if c.MigrationType == dcmTools.MigTypeRackspace { vlans, err = c.dcx.EnvironmentMigrationVlans(env) } else { vlans, err = c.db.CollectVlanTranslations(bson.M{"_id": env}) } if err != nil { resp.Missed = append( resp.Missed, collection.Missed{Item: 0, Details: fmt.Sprintf("em.EnvironmentMigrationVlans %s %v", env, err.Error())}, ) continue } if len(vlans) == 0 { resp.Missed = append(resp.Missed, collection.Missed{Item: 0, Details: fmt.Sprintf("No vlan information for %s", env)}) continue } if !xlatesFound(vlans) { resp.Missed = append( resp.Missed, collection.Missed{Item: 0, Details: fmt.Sprintf("no translations found for %s %d vlans", env, len(vlans))}, ) } var vlanList []VLAN // Gather the DRAC IP information for _, vlan := range vlans { vlanList = append(vlanList, VLAN{ Source: VlanStruct{ Name: vlan.ConfiguredVlan.ZoneName, Number: vlan.ConfiguredVlan.VlanNumber, Segments: strings.Join(vlan.ConfiguredVlan.NetworkSegments, ","), }, Migration: VlanStruct{ Name: vlan.MigrationVlan.ZoneName, Number: vlan.MigrationVlan.VlanNumber, Segments: strings.Join(vlan.MigrationVlan.NetworkSegments, ","), }, }) } uErr := c.db.UpdateMany( c.Collection, bson.D{{"environment.number", env}}, bson.D{{"$set", bson.D{{"network.vlans", vlanList}}}}, false, nil, ) if uErr != nil { resp.Missed = append(resp.Missed, collection.Missed{Item: 0, Details: fmt.Sprintf("mongo.UpdateMany %s %v", err, uErr.Error())}) continue } resp.Updated++ } chMissed <- resp.Missed } // saving existing values for non-rax migrations so we don't wipe data unnecessarily // in retrospect, I don't love this data structure func (c *Client) newIPStruct(item dcmTools.Device) IPStruct { return IPStruct{ IPv6: make([]dcmTools.IPv6AddressDetails, 0), DRAC: dcmTools.IPMapping{ Source: func() []dcmTools.IPAddressDetails { if c.MigrationType != dcmTools.MigTypeRackspace && len(item.Network.Address.DracIPs.Source) > 0 { return item.Network.Address.DracIPs.Source } return make([]dcmTools.IPAddressDetails, 0) }(), Migration: make([]dcmTools.IPAddressDetails, 0), }, ServiceNet: dcmTools.IPMapping{ Source: func() []dcmTools.IPAddressDetails { if c.MigrationType != dcmTools.MigTypeRackspace && len(item.Network.Address.ServiceNetIPs.Source) > 0 { return item.Network.Address.ServiceNetIPs.Source } return make([]dcmTools.IPAddressDetails, 0) }(), Migration: make([]dcmTools.IPAddressDetails, 0), }, VMManagement: dcmTools.IPMapping{ Source: func() []dcmTools.IPAddressDetails { if c.MigrationType != dcmTools.MigTypeRackspace && len(item.Network.Address.VMNetMgmt.Source) > 0 { return item.Network.Address.VMNetMgmt.Source } return make([]dcmTools.IPAddressDetails, 0) }(), Migration: make([]dcmTools.IPAddressDetails, 0), }, VMotion: dcmTools.IPMapping{ Source: func() []dcmTools.IPAddressDetails { if c.MigrationType != dcmTools.MigTypeRackspace && len(item.Network.Address.VMNetVMotion.Source) > 0 { return item.Network.Address.VMNetVMotion.Source } return make([]dcmTools.IPAddressDetails, 0) }(), Migration: make([]dcmTools.IPAddressDetails, 0), }, MBU: dcmTools.IPMapping{ Source: func() []dcmTools.IPAddressDetails { if c.MigrationType != dcmTools.MigTypeRackspace && len(item.Network.Address.MBUIPs.Source) > 0 { return item.Network.Address.MBUIPs.Source } return make([]dcmTools.IPAddressDetails, 0) }(), Migration: make([]dcmTools.IPAddressDetails, 0), }, } } func (i *IPStruct) setVMNet(ip dcxAutohost.PrivateIP) { if ip.Label == "vmnet_management" { if IsMigrationIP(ip.Zone) { i.VMManagement.Migration = append(i.VMManagement.Migration, setPrivAddr(ip)) return } i.VMManagement.Source = append(i.VMManagement.Source, setPrivAddr(ip)) } if ip.Label == "vmnet_vmotion" { if IsMigrationIP(ip.Zone) { i.VMotion.Migration = append(i.VMotion.Migration, setPrivAddr(ip)) return } i.VMotion.Source = append(i.VMotion.Source, setPrivAddr(ip)) } } func (i *IPStruct) setMBU(ip dcxAutohost.PrivateIP) { i.MBU.Source = append(i.MBU.Source, setPrivAddr(ip)) } func (i *IPStruct) setDRAC(ip dcxAutohost.PublicIP, migType string) { if ip.Reserved { i.DRAC.Migration = append(i.DRAC.Migration, setPubAddr(ip)) return } if migType == dcmTools.MigTypeRackspace { i.DRAC.Source = append(i.DRAC.Source, setPubAddr(ip)) return } log.Printf("%s: reserve flag not set and non-rax migration, skipping", ip.IPAddress) } func (i *IPStruct) setPrivDRAC(ip dcxAutohost.PrivateIP, migType string) { if ip.Reserved || IsMigrationIP(ip.Zone) { i.DRAC.Migration = append(i.DRAC.Migration, setPrivAddr(ip)) return } if migType == dcmTools.MigTypeRackspace { i.DRAC.Source = append(i.DRAC.Source, setPrivAddr(ip)) return } log.Printf("%s: reserve flag not set and non-rax migration, skipping", ip.IPAddress) } func (i *IPStruct) setServiceNet(ip dcxAutohost.PrivateIP) { if IsMigrationIP(ip.Zone) { i.ServiceNet.Migration = append(i.ServiceNet.Migration, setPrivAddr(ip)) return } i.ServiceNet.Source = append(i.ServiceNet.Source, setPrivAddr(ip)) } func (i *IPStruct) setPublicServiceNet(ip dcxAutohost.PublicIP) { if IsMigrationIP(ip.Zone) { i.ServiceNet.Migration = append(i.ServiceNet.Migration, setPubAddr(ip)) return } i.ServiceNet.Source = append(i.ServiceNet.Source, setPubAddr(ip)) } func setPrivAddr(ip dcxAutohost.PrivateIP) dcmTools.IPAddressDetails { return dcmTools.IPAddressDetails{ Netmask: ip.Netmask, IPAddress: ip.IPAddress, Gateway: ip.GatewayIP, Vlan: func() int { if ip.VLANNumber == nil { return 0 } return *ip.VLANNumber }(), Zone: ip.Zone, } } func setPubAddr(ip dcxAutohost.PublicIP) dcmTools.IPAddressDetails { return dcmTools.IPAddressDetails{ Netmask: ip.Netmask, IPAddress: ip.IPAddress, Gateway: ip.GatewayIP, Vlan: func() int { if ip.VLANNumber == nil { return 0 } return *ip.VLANNumber }(), Zone: ip.Zone, } } // xlatesFound function will take a list of type EnvironmentMigrationVlan's and return true // if there are any defined. Essentially, this will determine if migration VLANs have been // assigned yet. func xlatesFound(vlans []dcxAutohost.EnvironmentMigrationVlan) bool { for _, vlan := range vlans { if vlan.MigrationVlan.VlanNumber != 0 { return true } } return false }