// 2019 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 hypsnet will pretty print out the port configurations needed to apply all // ServiceNet vlans down to hypervisors ServiceNet ports. package hypsnet import ( "encoding/json" "fmt" "io" "os" "strings" "text/template" "github.com/sirupsen/logrus" "github.rackspace.com/DCM/gomigrate/core" "github.rackspace.com/DCM/gomigrate/shared" "github.rackspace.com/DCM/tools" "github.rackspace.com/gorax/clients/mongodbAPI" "github.rackspace.com/gorax/gokeys" ) var ( argMoveGroups *[]string // hypProcessTypes are a listing of process.os_type models we will pull from the // migration DB. If the type is not in this list, it will not be considered as a HYP. hypProcessTypes = []string{ "VMWare ESX Account", "VMWare ESX", "Hyper-V", } sNetIgnoreVlans = []int{ 4021, 4022, 4024, 90, 900, 901, 902, 903, 904, 905, 906, 907, } ) type ( envConfig struct { Number string SNetVlans []tools.VlanTranslation VlanList []string SwitchCmds map[string][]string Devices []tools.Device } ) // Plugin function will return a argparse.Command type back to the parent parser // nolint:typecheck func Plugin(p *core.Parser) core.Plugin { // Create a argument for the Hyp ServiceNet config logic cmd := p.NewCommand("hyp_snet", "Print out ServiceNet config for all hypervisors in a move-group") argMoveGroups = shared.ArgMoveGroups(cmd) return core.Plugin{CMD: cmd, Func: pluginFunc} } // Run is the main entry point function and will take a move-group to determine servers or net-devices // that are assign a port configuration that could possibly be a complicated assignment and require // manual review. func pluginFunc(cfg gokeys.Settings) { // Pull a core token from the users gokeys keychain cKey, err := cfg.Core(false) if err != nil { logrus.Fatal(err) } moveGroupList := *argMoveGroups if len(moveGroupList) == 0 { logrus.Fatalf("no move-groups found in %s", moveGroupList) } // Determine the mongo collection we should be querying based on the move-group // name. collection := shared.GetCollection(moveGroupList[0]) if collection == "" { logrus.Fatalf("unable to parse collection name from %s", moveGroupList[0]) } logrus.Infof("Gathering device list for %s in %s", strings.Join(moveGroupList, ","), collection) for _, moveGroup := range moveGroupList { // Pull a list of network devices ONLY for us to run through the failover_device call. deviceList, err := getDeviceList(moveGroup, cKey.Password, collection) if err != nil { logrus.Error(err) continue } for _, config := range parseServiceNet(deviceList, collection) { config.buildSwitchCmds() if err := config.printTemplate(os.Stdout); err != nil { logrus.Errorf("unable to parse config into template:%s", err) } } } } // prettyPrintVlans func will take a structured type of response data and render a table // output to stdout. func (e *envConfig) printTemplate(w io.Writer) error { // create a new template with some name t := template.New(e.Number) //parse some content and generate a template t, err := t.Parse(shared.HypSNetTmpl) if err != nil { return err } return t.Execute(w, e) } func (e *envConfig) getVlanList() { var ints = make([]int, 0) for _, vlan := range e.SNetVlans { ints = append(ints, vlan.Source.VlanNumber) } e.VlanList = shared.ConcatVLANs(ints) } func (e *envConfig) buildSwitchCmds() { e.SwitchCmds = make(map[string][]string) e.getVlanList() for _, d := range e.Devices { for _, port := range d.Environment.Migration.PortsParsed { if !strings.Contains(strings.ToLower(port.PortType), "servicenet") { continue } swName := fmt.Sprintf("%s.%s", port.SwitchName, port.DataCenter) if _, exist := e.SwitchCmds[swName]; !exist { e.SwitchCmds[swName] = []string{ "!", fmt.Sprintf("nscli switch %s", swName), " configure terminal", } } portConfig := []string{ fmt.Sprintf(" interface Gi1/0/%s", port.PortNumber), fmt.Sprintf(" ! Note: For device %s - %s", d.ID, d.Device.MakeModel), " switch trunk allowed vlan none", fmt.Sprintf(" switchport trunk allowed vlan add %s", strings.Join(e.VlanList, ",")), " switchport mode trunk", " exit", } e.SwitchCmds[swName] = append(e.SwitchCmds[swName], portConfig...) } } } // getDeviceList will call out to Mongo to pull a list of devices within a move-group // and filter out just network devices that are targeted in shutdown. func getDeviceList(moveGroup, token, collection string) ([]tools.Device, error) { // Create a new Mongo Client c := mongodbAPI.New(mongodbAPI.WithToken(token), "migration", false) // Initiate the call out to the migration/Find resource byt, err := c.Find( &mongodbAPI.FindQuery{ Query: map[string]interface{}{ "move_group": moveGroup, "deleted": map[string]bool{"$ne": true}, "process.os_type": map[string]interface{}{"$in": hypProcessTypes}, "environment.migration.switch_ports_parsed": map[string]interface{}{"$nin": []interface{}{"", nil}}, "network.vlans": map[string]interface{}{"$nin": []interface{}{"", nil}}, }, Projection: []string{ "account.number", "account.name", "device.make_model", "environment.migration.switch_ports_parsed", "environment.number", "network.vlans", "move_group", }, }, collection) if err != nil { return []tools.Device{}, fmt.Errorf("getDeviceList:mongodbAPI.Find:%s", err) } // Decode the json response back var payload struct { Result []tools.Device `json:"result"` } jd := json.NewDecoder(strings.NewReader(string(byt))) if err = jd.Decode(&payload); err != nil { return []tools.Device{}, err } return payload.Result, nil } func parseServiceNet(devices []tools.Device, migrDC string) []envConfig { var ( // Store all vlans by environment vlanMap = make(map[string][]tools.VlanTranslation) // Store all devices by environment devMap = make(map[string][]tools.Device) // Create the response value resp = make([]envConfig, 0) ) for _, device := range devices { // Categorize all the hypervisors by environment number devMap[device.Environment.Number] = append(devMap[device.Environment.Number], device) VlanLoop: for _, vlan := range device.Network.Vlans { // If the vlan already exist in the list of vlans for the environment // skip adding it again. if shared.VlanInSlice(vlan, vlanMap[device.Environment.Number]) { continue } // If the zone name is not named with the migration DC, skip it. // E.g. IAD3:Servicenet:501-9 migrDCIdentifier := strings.Split(migrDC, "_")[0] if !strings.Contains(strings.ToLower(vlan.Source.ZoneName), migrDCIdentifier) { continue } // If the zone name is not a ServiceNet zone, skip it. // E.g. IAD3:Servicenet:501-9 if !strings.Contains(strings.ToLower(vlan.Source.ZoneName), "servicenet") { continue } // If the network-segment is a DRAC segment, skip it. // E.g. Vlan905:DRAC_10.4.235.0/24 if strings.Contains(strings.ToLower(vlan.Source.NetworkSegments), "drac") { continue } // If the network-segment is a VMNet segment, skip it. // E.g. Vlan905:DRAC_10.4.235.0/24 if strings.Contains(strings.ToLower(vlan.Source.NetworkSegments), "vm") { continue } // Look through the list of vlan ids that are reserved for other // services and skip if matched. for _, vlanID := range sNetIgnoreVlans { if vlan.Source.VlanNumber == vlanID { continue VlanLoop } } // If the network-segment is not a generic vlan name or snet named, we should skip it. if !strings.Contains(strings.ToLower(vlan.Source.NetworkSegments), "vlan") && !strings.Contains(strings.ToLower(vlan.Source.NetworkSegments), "snet") { continue } // If we get to this point, add the Vlan segment onto the environments ServiceNet // Vlan list. vlanMap[device.Environment.Number] = append(vlanMap[device.Environment.Number], vlan) } } for env, vlans := range vlanMap { resp = append(resp, envConfig{Number: env, SNetVlans: vlans, Devices: devMap[env]}) } return resp }