Skip to content

Commit

Permalink
configurable columns, attach/detach by volume and server name
Browse files Browse the repository at this point in the history
  • Loading branch information
sbueringer committed Oct 13, 2019
1 parent 0608138 commit c6ab6f9
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 51 deletions.
2 changes: 1 addition & 1 deletion pkg/cmd/lb.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func (o *LBOptions) Run() error {
// multiple tenants
// disable header here and print them once if required
if !o.noHeader {
output, err := output.ConvertToTable(output.Table{volumeHeaders, [][]string{}, []int{0, 1}, o.output})
output, err := output.ConvertToTable(output.Table{defaultHeaders, [][]string{}, []int{0, 1}, o.output})
if err != nil {
return fmt.Errorf("error creating output: %v", err)
}
Expand Down
62 changes: 57 additions & 5 deletions pkg/cmd/volume-fix.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"fmt"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/sbueringer/kubectl-openstack-plugin/pkg/kubernetes"
"github.com/sbueringer/kubectl-openstack-plugin/pkg/openstack"
Expand Down Expand Up @@ -67,11 +68,11 @@ func NewCmdVolumesFix(streams genericclioptions.IOStreams) *cobra.Command {
// Cinder: https://developer.openstack.org/api-ref/block-storage/v3/index.html?expanded=detach-volume-from-server-detail#volume-actions-volumes-action
// https://raymii.org/s/articles/Fix_inconsistent_Openstack_volumes_and_instances_from_Cinder_and_Nova_via_the_database.html
cmd.Flags().BoolVarP(&o.detachCinder, "detach-cinder", "", false, "Detach the disk in Cinder. Be careful this does not remove the attachment from the server in Nova.")
cmd.Flags().StringVarP(&o.attachCinder, "attach-cinder", "", "", "")
cmd.Flags().StringVarP(&o.attachCinder, "attach-cinder", "", "", "server to which the volume to")
cmd.Flags().StringVarP(&o.attachCinderMountpoint, "attach-cinder-mountpoint", "", "", "")

cmd.Flags().BoolVarP(&o.detachNova, "detach-nova", "", false, "Detach disk in Nova. This only works if the volume is really attached (so it doesn't when cinder shows no attachments to this server).")
cmd.Flags().StringVarP(&o.attachNova, "attach-nova", "", "", "")
cmd.Flags().StringVarP(&o.attachNova, "attach-nova", "", "", "server to which the volume to")
cmd.Flags().BoolVarP(&o.force, "force", "f", false, "Currently only affects detach-cinder. Use force-detach.")
o.configFlags.AddFlags(cmd.Flags())
return cmd
Expand Down Expand Up @@ -133,8 +134,18 @@ func (o *VolumesFixOptions) runWithConfig(context string) error {
return fmt.Errorf("error getting servers from OpenStack: %v", err)
}

// resolve volume ids, if id is not of expected lenght try to find via name
var vIDs []string
for _, arg := range o.args {
vID, err := resolveVolume(volumesMap, arg)
if err != nil {
return fmt.Errorf("error finding volume %v: %v", arg, err)
}
vIDs = append(vIDs, vID)
}

// loop over volumes
for _, vID := range o.args {
for _, vID := range vIDs {
volume, ok := volumesMap[vID]
if !ok {
fmt.Printf("Volume with id %s not found\n", vID)
Expand All @@ -149,24 +160,36 @@ func (o *VolumesFixOptions) runWithConfig(context string) error {
}
}

// attach via Cinder
if o.attachCinder != "" && o.attachCinderMountpoint != "" {
err := openstack.AttachVolumeCinder(osProvider, volume.ID, o.attachCinder, o.attachCinderMountpoint)
serverID, err := resolveServer(serversMap, o.attachCinder)
if err != nil {
return err
}
err = openstack.AttachVolumeCinder(osProvider, volume.ID, serverID, o.attachCinderMountpoint)
if err != nil {
return err
}
}
// attach via Nova
if o.attachNova != "" {
err := openstack.AttachVolumeNova(osProvider, volume.ID, o.attachNova)
serverID, err := resolveServer(serversMap, o.attachNova)
if err != nil {
return err
}
err = openstack.AttachVolumeNova(osProvider, volume.ID, serverID)
if err != nil {
return err
}
}
// detach via Cinder
if o.detachCinder {
err := openstack.DetachVolumeCinder(osProvider, volume.ID, o.force)
if err != nil {
return err
}
}
// detach via Nova
if o.detachNova {
uniqueServerIDs := map[string]bool{}
for _, srv := range srvs {
Expand All @@ -187,3 +210,32 @@ func (o *VolumesFixOptions) runWithConfig(context string) error {

return nil
}

func resolveVolume(volumes map[string]volumes.Volume, idOrName string) (string, error) {
// volumeIDs have a length of 36
if len(idOrName) == 36 {
return idOrName, nil
}

for _, v := range volumes {
if v.Name == idOrName {
return v.ID, nil
}
}
return "", fmt.Errorf("could not find volume with id or name: %s", idOrName)
}

func resolveServer(serversMap map[string]servers.Server, idOrName string) (string, error) {
// serverIDs have a length of 36
if len(idOrName) == 36 {
return idOrName, nil
}

for _, s := range serversMap {
if s.Name == idOrName {
return s.ID, nil
}
}
return "", fmt.Errorf("could not find server with id or name: %s", idOrName)
}

103 changes: 58 additions & 45 deletions pkg/cmd/volume.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

package cmd

import (
Expand Down Expand Up @@ -30,6 +31,7 @@ type VolumesOptions struct {
exporter string
output string
noHeader bool
columns string
args []string
onlyBroken bool
debug bool
Expand Down Expand Up @@ -75,13 +77,18 @@ func NewCmdVolumes(streams genericclioptions.IOStreams) *cobra.Command {
cmd.Flags().StringVar(&o.states, "states", "", "filter by states, default list all")
cmd.Flags().StringVarP(&o.exporter, "exporter", "e", "stdout", "stdout, mm or multiple (comma-separated)")
cmd.Flags().StringVarP(&o.output, "output", "o", "markdown", "markdown or raw")
cmd.Flags().BoolVarP(&o.debug, "debug", "", false, "debug prints more columns")
cmd.Flags().BoolVarP(&o.debug, "debug", "", false, "debug prints debug columns, equivalent to --columns=DEBUG")
cmd.Flags().BoolVarP(&o.onlyBroken, "only-broken", "", false, "only show disks which are broken/out of sync")
cmd.Flags().BoolVarP(&o.noHeader, "no-headers", "", false, "hide table headers")
cmd.Flags().StringVar(&o.columns, "columns", strings.Join(defaultHeaders, ","), fmt.Sprintf("column-separated list of headers to show, if set to DEBUG a special debug subset of columns is shown. The following columns are availble: %v", allHeaders))
o.configFlags.AddFlags(cmd.Flags())
return cmd
}

var defaultHeaders = []string{"PVC", "POD", "POD_NODE", "POD_STATUS", "CINDER_NAME", "SIZE", "CINDER_ID", "CINDER_SERVER", "CINDER_SERVER_ID", "CINDER_STATUS"}
var debugHeaders = []string{"PVC", "PV", "POD", "POD_NODE", "POD_STATUS", "CINDER_NAME", "CINDER_ID", "CINDER_SERVER", "CINDER_SERVER_ID", "CINDER_STATUS", "NOVA_SERVER", "NOVA_SERVER_ID", "NOTE"}
var allHeaders = []string{"CLUSTER", "PVC", "PV", "POD", "POD_NODE", "POD_STATUS", "CINDER_NAME", "SIZE", "CINDER_ID", "CINDER_SERVER", "CINDER_SERVER_ID", "CINDER_STATUS", "NOVA_SERVER", "NOVA_SERVER_ID", "NOTE"}

// Complete sets als necessary fields in VolumeOptions
func (o *VolumesOptions) Complete(cmd *cobra.Command, args []string) error {
o.args = args
Expand All @@ -91,6 +98,9 @@ func (o *VolumesOptions) Complete(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
if o.debug || o.columns == "DEBUG" {
o.columns = strings.Join(debugHeaders, ",")
}
return nil
}

Expand Down Expand Up @@ -118,13 +128,7 @@ func (o *VolumesOptions) Run() error {
// multiple tenants
// disable header here and print them once if required
if !o.noHeader {
var header []string
if o.debug {
header = volumeDebugHeaders
} else {
header = volumeHeaders
}
output, err := output.ConvertToTable(output.Table{header, [][]string{}, []int{0, 1}, o.output})
output, err := output.ConvertToTable(output.Table{strings.Split(o.columns, ","), [][]string{}, []int{0, 1}, o.output})
if err != nil {
return fmt.Errorf("error creating output: %v", err)
}
Expand Down Expand Up @@ -222,22 +226,30 @@ func (o *VolumesOptions) runWithConfig(context string) error {
return nil
}

var volumeHeaders = []string{"CLUSTER", "PVC", "POD", "POD_NODE", "POD_STATUS", "CINDER_NAME", "SIZE", "CINDER_ID", "CINDER_SERVER", "CINDER_SERVER_ID", "CINDER_STATUS"}
var volumeDebugHeaders = []string{"CLUSTER", "PVC", "PV", "POD", "POD_NODE", "POD_STATUS", "CINDER_NAME", "SIZE", "CINDER_ID", "CINDER_SERVER", "CINDER_SERVER_ID", "CINDER_STATUS", "NOVA_SERVER", "NOVA_SERVER_ID", "NOTE"}

func (o *VolumesOptions) getPrettyVolumeList(context string, pvs map[string]v1.PersistentVolume, podMap map[string][]v1.Pod, volumes map[string]volumes.Volume, server map[string]servers.Server, attachmentsMap map[string]*openstack.NovaVolumeAttachments) (string, error) {

var header []string
if !o.noHeader {
if o.debug {
header = volumeDebugHeaders
} else {
header = volumeHeaders
}
header = strings.Split(o.columns, ",")
}

var lines [][]string
linesAllColumns := map[string]map[string]string{}
for _, v := range volumes {

// Skip disk if it's status doesn't match one of the states defined in the state flag
if o.states != "" {
var matchesStates bool
for _, state := range strings.Split(o.states, ",") {
if v.Status == state {
matchesStates = true
break
}
}
if !matchesStates {
continue
}
}

var cinderServers []string
var cinderServerIDs []string
var novaServers []string
Expand Down Expand Up @@ -282,66 +294,67 @@ func (o *VolumesOptions) getPrettyVolumeList(context string, pvs map[string]v1.P
}
}

matchesStates := false
for _, state := range strings.Split(o.states, ",") {
if v.Status == state {
matchesStates = true
break
}
}

var notes []string
showDiskIfOnlyBroken := false
// check error states
if overallNovaAttachmentCount >= 2 {
showDiskIfOnlyBroken = true
notes = append(notes, "multiple attachments")
}
if podNode != "-" && podStatus != "Completed" && !strings.Contains(strings.Join(cinderServers, " "), podNode) {
showDiskIfOnlyBroken = true
notes = append(notes, "pod != cinder server")
}
if podNode != "-" && podStatus != "Completed" && !strings.Contains(strings.Join(novaServers, " "), podNode) {
showDiskIfOnlyBroken = true
notes = append(notes, "pod != nova server")
}
if !strings.Contains(strings.Join(novaServers, " "), strings.Join(cinderServers, " ")) {
showDiskIfOnlyBroken = true
notes = append(notes, "nova != cinder server")
}
if v.Status == "available" && (len(novaServers) > 0 || len(cinderServers) > 0) {
showDiskIfOnlyBroken = true
notes = append(notes, "available but attached")
}
if v.Status == "available" && podName != "-" && podStatus != "Completed" {
showDiskIfOnlyBroken = true
notes = append(notes, fmt.Sprintf("available but pod %q", podStatus))
}
if v.Status == "in-use" && (len(novaServers) == 0 || len(cinderServers) == 0) {
showDiskIfOnlyBroken = true
notes = append(notes, "in-use but not attached")
}
if strings.Contains(strings.Join(cinderServers, " "), "not found") {
showDiskIfOnlyBroken = true
notes = append(notes, "attached server not found")
}
if pvClaim == "-" && pvName == "-" && podName == "-" && strings.HasPrefix(v.Name, "kubernetes-dynamic-pvc") {
showDiskIfOnlyBroken = true
notes = append(notes, "kubernetes disk has no pv/pvc/pod")
}
note := strings.Join(notes, ", ")

if (!o.onlyBroken || showDiskIfOnlyBroken) && (matchesStates || o.states == "") {
if o.debug {
lines = append(lines, []string{context, pvClaim, pvName, podName, podNode, podStatus,
v.Name, fmt.Sprintf("%d", v.Size), v.ID, strings.Join(cinderServers, " "), strings.Join(cinderServerIDs, " "), v.Status,
strings.Join(novaServers, " "), strings.Join(novaServerIDs, " "), note,
})
} else {
lines = append(lines, []string{context, pvClaim, podName, podNode, podStatus,
v.Name, fmt.Sprintf("%d", v.Size), v.ID, strings.Join(cinderServers, " "), strings.Join(cinderServerIDs, " "), v.Status,
})
lineAllColumns := map[string]string{}
// var allHeaders = []string{"CLUSTER", "PVC", "PV", "POD", "POD_NODE", "POD_STATUS", "CINDER_NAME", "SIZE", "CINDER_ID", "CINDER_SERVER", "CINDER_SERVER_ID", "CINDER_STATUS", "NOVA_SERVER", "NOVA_SERVER_ID", "NOTE"
lineAllColumns["CLUSTER"] = context
lineAllColumns["PVC"] = pvClaim
lineAllColumns["PV"] = pvName
lineAllColumns["POD"] = podName
lineAllColumns["POD_NODE"] = podNode
lineAllColumns["POD_STATUS"] = podStatus
lineAllColumns["CINDER_NAME"] = v.Name
lineAllColumns["SIZE"] = fmt.Sprintf("%d", v.Size)
lineAllColumns["CINDER_ID"] = v.ID
lineAllColumns["CINDER_SERVER"] = strings.Join(cinderServers, " ")
lineAllColumns["CINDER_SERVER_ID"] = strings.Join(cinderServerIDs, " ")
lineAllColumns["CINDER_STATUS"] = v.Status
lineAllColumns["NOVA_SERVER"] = strings.Join(novaServers, " ")
lineAllColumns["NOVA_SERVER_ID"] = strings.Join(novaServerIDs, " ")
lineAllColumns["NOTE"] = note

linesAllColumns[v.ID] = lineAllColumns
}

var lines [][]string
for _, allColumns := range linesAllColumns {
_, containsNote := allColumns["NOTE"]
if !o.onlyBroken || containsNote {
var lineColumns []string
for _, column := range strings.Split(o.columns, ",") {
lineColumns = append(lineColumns, allColumns[column])
}
lines = append(lines, lineColumns)
}
}
if len(lines) > 0 {
Expand Down

0 comments on commit c6ab6f9

Please sign in to comment.