// 2020 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 drac import ( "log" "net" "os" "strings" "github.rackspace.com/DCM/tools" "github.com/jokelyo/argparse" "github.com/sirupsen/logrus" input "github.com/tcnksm/go-input" "github.rackspace.com/DCM/gomigrate/core" "github.rackspace.com/DCM/gomigrate/shared" "github.rackspace.com/gorax/clients/dcxAutohost" "github.rackspace.com/gorax/gokeys" ) const dracNet = "DRACNet" var ( argAllMigration *bool argDeallocate *bool argReserve *bool argD *[]int argIP *string argM *[]string ) // SubPlugin function will return a argparse.Command type back to the parent parser // nolint:typecheck func SubPlugin(p *argparse.Command) core.Plugin { // Create a command and argument for the ip audit cmd := p.NewCommand("drac", "Manage DRAC IP Assignments") argD = shared.ArgDevices(cmd) argIP = cmd.String("", "ip", &argparse.Options{Help: "IP Address"}) argM = shared.ArgMoveGroups(cmd) argDeallocate = cmd.Flag("", "deallocate", &argparse.Options{Help: "Deallocate IP from device"}) argReserve = cmd.Flag("", "reserve", &argparse.Options{Help: "Reserve DRAC IP for device"}) argAllMigration = cmd.Flag("", "all-migration", &argparse.Options{Help: "Used with --deallocate to remove all migration IPs"}) return core.Plugin{CMD: cmd, Func: pluginFunc} } type ( client struct { dcx *dcxAutohost.Client devices []int movegroups []string coreToken string identityToken string } ) // pluginFunc function is executed from the gomigrate caller func pluginFunc(cfg gokeys.Settings) { // Set all the key from the arg parser c := client{ devices: *argD, movegroups: *argM, } if !*argDeallocate && !*argReserve { logrus.Fatal("Must select either --deallocate or --reserve") } // Pull an identity token from the users gokeys keychain key, err := cfg.Identity(false) if err != nil { log.Fatal(err) } c.identityToken = key.Password // Pull a core token from the gokeys keychain coreKey, err := cfg.Core(false) if err != nil { log.Fatal(err) } c.coreToken = coreKey.Password if len(c.devices) == 0 && len(c.movegroups) == 0 { logrus.Fatal("Must provide device(s) or move group(s)") } // Get the device list from a list of move-groups or devices. devices, err := shared.MongoDevices(c.movegroups, nil, c.devices, "", coreKey.Password, make(map[string]interface{})) if err != nil { logrus.Fatal(err) } // Stop here if we did not find any devices. if len(devices) == 0 { logrus.Fatalf("no DB devices found in devices:(%v)", c.devices) } // Grab the client for the DCX API c.dcx, err = dcxAutohost.New(dcxAutohost.WithToken(c.identityToken), false) if err != nil { logrus.Fatal(err) } cmd := "deallocate" if *argReserve { cmd = "reserve" } logrus.Infof("Running %s DRAC for %d device(s)...", cmd, len(devices)) useIP := true if len(devices) > 1 && *argIP != "" { logrus.Info("multiple devices, ignoring --ip argument") useIP = false } for _, d := range devices { if !hasDracPort(d) { logrus.Warnf("%d: DRACNet port not found, skipping.", d.ID) continue } if *argDeallocate { c.deallocate(d, useIP) } else if *argReserve { c.reserve(d, useIP) } logrus.Println() } } func (c *client) deallocate(d tools.Device, useIP bool) { if *argAllMigration { if len(d.Network.Address.DracIPs.Migration) < 1 { return } logrus.Printf("%d: Deallocating all migration DRAC IPs found in DB\n", d.ID) for _, x := range d.Network.Address.DracIPs.Migration { ip := net.ParseIP(x.IPAddress) if ip == nil { logrus.Errorf("%d: Error parsing IP [%s], skipping", d.ID, x.IPAddress) } c.runDeallocate(d.ID, ip) } return } ip := net.ParseIP("farts") if useIP && *argIP != "" { ip = net.ParseIP(*argIP) if ip == nil { logrus.Errorf("%d: Failed to parse --ip value", d.ID) } } if ip == nil { ipList := getDracIPs(d) if len(ipList) == 0 { logrus.Errorf("%d: No DRAC IPs found in DB.", d.ID) return } logrus.Printf("%d: DRAC IPs found in DB:\n", d.ID) for _, x := range ipList { logrus.Println(x) } logrus.Print("\n") var err error var selectedIP string ui := &input.UI{Writer: os.Stdout, Reader: os.Stdin} for { if selectedIP == "0" { return } if selectedIP != "" { ip = net.ParseIP(selectedIP) if ip != nil { break } } selectedIP, err = ui.Ask("IP to deallocate (0 to skip)", &input.Options{Required: true, HideOrder: true}) if err != nil { logrus.Errorf("%d: Error: %s", d.ID, err.Error()) return } } } c.runDeallocate(d.ID, ip) } func (c *client) runDeallocate(device int, ip net.IP) { opts := dcxAutohost.DeallocateOptions{} err := c.dcx.DeviceDeallocateIPAddress(device, opts.SetDRACNet(true).SetIPAddress(ip)) if err != nil { logrus.Errorf("%d: Error: %s\n", device, err.Error()) return } logrus.Infof("%d: IP %s deallocated.\n", device, ip.String()) } func (c *client) reserve(d tools.Device, useIP bool) { if useIP && *argIP != "" { ip := net.ParseIP(*argIP) if ip == nil { logrus.Errorf("%d: Failed to parse --ip value", d.ID) } else { err := c.dcx.DeviceManualAssignIP(d.ID, "", ip.String(), false, true) if err == nil { logrus.Infof("%d: IP address %s reserved", d.ID, ip.String()) } else { logrus.Errorf("%d: Error: %s", d.ID, err.Error()) } return } } if len(d.Network.Address.DracIPs.Migration) > 0 { logrus.Infof("%d: Migration DRAC already exists in DB, skipping.", d.ID) return } // for non-rackspace devices, enable reserve flag on existing src drac if d.Linked.ID != 0 && len(d.Network.Address.DracIPs.Source) > 0 { for _, x := range d.Network.Address.DracIPs.Source { srcIP := net.ParseIP(x.IPAddress) if srcIP == nil { logrus.Errorf("%d: failure parsing source DRAC IP %s", d.ID, x.IPAddress) return } opts := dcxAutohost.DeallocateOptions{} if err := c.dcx.DeviceDeallocateIPAddress(d.ID, opts.SetDRACNet(true).SetIPAddress(srcIP)); err != nil { logrus.Errorf("%d: failed to deallocate IP %s", d.ID, srcIP.String()) } err := c.dcx.DeviceManualAssignIP(d.ID, "", srcIP.String(), false, true) if err == nil { logrus.Infof("%d: IP address %s reserved", d.ID, srcIP.String()) } else { logrus.Errorf("%d: Error: %s", d.ID, err.Error()) } } return } // reservation is true for rax devices; they will have mig ports resp, err := c.dcx.DeviceAutoAssignDracIP(d.ID, d.Linked.ID == 0) if err == nil { if d.Linked.ID == 0 { logrus.Infof("%d: IP address %s reserved", d.ID, resp.DracIPAddress) } else { // for non-rax devices, now we have to immediately deallocate and then manually assign with reserve true ip := net.ParseIP(resp.DracIPAddress) if ip == nil { logrus.Errorf("%d: failed to parse newly assigned IP %s", d.ID, resp.DracIPAddress) } opts := dcxAutohost.DeallocateOptions{} if err := c.dcx.DeviceDeallocateIPAddress(d.ID, opts.SetDRACNet(true).SetIPAddress(ip)); err != nil { logrus.Errorf("%d: failed to deallocate IP %s", d.ID, ip.String()) } err := c.dcx.DeviceManualAssignIP(d.ID, "", ip.String(), false, true) if err == nil { logrus.Infof("%d: IP address %s reserved", d.ID, ip.String()) } else { logrus.Errorf("%d: Error: %s", d.ID, err.Error()) } } } else { logrus.Errorf("%d: Error: %s", d.ID, err.Error()) } } func getDracIPs(d tools.Device) []string { ipList := make([]string, 0) for _, x := range d.Network.Address.DracIPs.Source { ipList = append(ipList, "src: "+x.IPAddress) } for _, x := range d.Network.Address.DracIPs.Migration { ipList = append(ipList, "mig: "+x.IPAddress) } return ipList } func hasDracPort(d tools.Device) bool { for _, p := range d.Environment.Migration.SwitchPorts { if strings.Contains(p, dracNet) { return true } } return false }