Skip to content

Commit

Permalink
Adds the debug info command
Browse files Browse the repository at this point in the history
  • Loading branch information
mik-dass committed Jan 21, 2020
1 parent a39da91 commit a2bae96
Show file tree
Hide file tree
Showing 7 changed files with 407 additions and 4 deletions.
124 changes: 124 additions & 0 deletions pkg/debug/info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package debug

import (
"encoding/json"
"errors"
"github.com/golang/glog"
"github.com/openshift/odo/pkg/occlient"
"github.com/openshift/odo/pkg/testingutil/filesystem"
"io/ioutil"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"net"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
)

type OdoDebugFile struct {
metav1.TypeMeta
DebugProcessId int
ProjectName string
AppName string
ComponentName string
RemotePort int
LocalPort int
}

// GetDebugInfoFilePath gets the file path of the debug info file
func GetDebugInfoFilePath(client *occlient.Client, componentName, appName string) string {
tempDir := os.TempDir()
debugFilePrefix := "odo-debug.json"
s := []string{client.Namespace, appName, componentName, debugFilePrefix}
debugFileName := strings.Join(s, "-")
return filepath.Join(tempDir, debugFileName)
}

func CreateDebugInfoFile(f *DefaultPortForwarder, portPair string) error {
return createDebugInfoFile(f, portPair, filesystem.DefaultFs{})
}

// createDebugInfoFile creates a file in the temp directory with information regarding the debugging session of a component
func createDebugInfoFile(f *DefaultPortForwarder, portPair string, fs filesystem.Filesystem) error {
portPairs := strings.Split(portPair, ":")
if len(portPairs) > 2 || len(portPairs) < 2 {
return errors.New("port pair should be of the format localPort:RemotePair")
}

localPort, err := strconv.Atoi(portPairs[0])
if err != nil {
return errors.New("local port should be a int")
}
remotePort, err := strconv.Atoi(portPairs[1])
if err != nil {
return errors.New("remote port should be a int")
}

odoDebugFile := OdoDebugFile{
TypeMeta: metav1.TypeMeta{},
DebugProcessId: os.Getpid(),
ProjectName: f.client.Namespace,
AppName: f.appName,
ComponentName: f.componentName,
RemotePort: remotePort,
LocalPort: localPort,
}
odoDebugPathData, err := json.Marshal(odoDebugFile)
if err != nil {
return errors.New("error marshalling json data")
}

// writes the data to the debug info file
file, err := fs.OpenFile(GetDebugInfoFilePath(f.client, f.componentName, f.appName), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
}
_, err = file.Write(odoDebugPathData)
if err != nil {
return err
}
return nil
}

// GetDebugInfo gets information regarding the debugging session of the component
func GetDebugInfo(f *DefaultPortForwarder) (int, bool) {
// gets the debug info file path and reads/unmarshals it
debugInfoFilePath := GetDebugInfoFilePath(f.client, f.componentName, f.appName)
odoDebugFileRead, err := ioutil.ReadFile(debugInfoFilePath)
if err != nil {
glog.V(4).Infof("the debug %v is not present", debugInfoFilePath)
return -1, false
}

var odoDebugFileData OdoDebugFile
err = json.Unmarshal(odoDebugFileRead, &odoDebugFileData)
if err != nil {
glog.V(4).Infof("couldn't unmarshal the debug file %v", debugInfoFilePath)
return -1, false
}

// get the debug process id and send a signal 0 to check if it's alive or not
processInfo, err := os.FindProcess(odoDebugFileData.DebugProcessId)
if err != nil {
glog.V(4).Infof("error getting the process info for pid %v", odoDebugFileData.DebugProcessId)
return -1, false
}

err = processInfo.Signal(syscall.Signal(0))
if err != nil {
glog.V(4).Infof("error sending signal 0 to pid %v, cause: %v", odoDebugFileData.DebugProcessId, err)
return -1, false
}

// gets the debug local port and dials it to check if the port is listening or not
addressLook := "localhost:" + strconv.Itoa(odoDebugFileData.LocalPort)
_, err = net.Dial("tcp", addressLook)
if err != nil {
glog.V(4).Infof("error dialing address %v, cause: %v", odoDebugFileData.DebugProcessId, err)
return -1, false
}

// returns the local port for further processing
return odoDebugFileData.LocalPort, true
}
117 changes: 117 additions & 0 deletions pkg/debug/info_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package debug

import (
"encoding/json"
"github.com/openshift/odo/pkg/occlient"
"github.com/openshift/odo/pkg/testingutil/filesystem"
"os"
"testing"
)

func Test_createDebugInfoFile(t *testing.T) {

// create a fake fs in memory
fs := filesystem.NewFakeFs()

type args struct {
defaultPortForwarder *DefaultPortForwarder
portPair string
fs filesystem.Filesystem
}
tests := []struct {
name string
args args
wantLocalPort int
wantRemotePort int
alreadyExistFile bool
wantErr bool
}{
{
name: "case 1: normal json write to the debug file",
args: args{
defaultPortForwarder: &DefaultPortForwarder{
componentName: "nodejs-ex",
appName: "app",
},
portPair: "5858:9001",
fs: fs,
},
wantLocalPort: 5858,
wantRemotePort: 9001,
alreadyExistFile: false,
wantErr: false,
},
{
name: "case 2: overwrite the debug file",
args: args{
defaultPortForwarder: &DefaultPortForwarder{
componentName: "nodejs-ex",
appName: "app",
},
portPair: "5758:9004",
fs: fs,
},
wantLocalPort: 5758,
wantRemotePort: 9004,
alreadyExistFile: true,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

// Fake the client with the appropriate arguments
client, _ := occlient.FakeNew()
client.Namespace = "testing-1"
tt.args.defaultPortForwarder.client = client

debugFilePath := GetDebugInfoFilePath(client, tt.args.defaultPortForwarder.componentName, tt.args.defaultPortForwarder.appName)
if tt.alreadyExistFile {
file, err := fs.Create(debugFilePath)
if err != nil {
t.Errorf("error happened while writing, cause: %v", err)
}
_, err = file.WriteString("blah")
if err != nil {
t.Errorf("error happened while writing, cause: %v", err)
}
}

if err := createDebugInfoFile(tt.args.defaultPortForwarder, tt.args.portPair, tt.args.fs); (err != nil) != tt.wantErr {
t.Errorf("createDebugInfoFile() error = %v, wantErr %v", err, tt.wantErr)
}

readBytes, err := fs.ReadFile(debugFilePath)
if err != nil {
t.Errorf("error while reading file, cause: %v", err)
}
var odoDebugFileData OdoDebugFile
err = json.Unmarshal(readBytes, &odoDebugFileData)
if err != nil {
t.Errorf("error occured while unmarshalling json, cause: %v", err)
}

if odoDebugFileData.LocalPort != tt.wantLocalPort {
t.Errorf("the local port on the file doesn't match, got %v, want %v", odoDebugFileData.LocalPort, tt.wantLocalPort)
}
if odoDebugFileData.RemotePort != tt.wantRemotePort {
t.Errorf("the remote port on the file doesn't match, got %v, want %v", odoDebugFileData.RemotePort, tt.wantRemotePort)
}
if odoDebugFileData.DebugProcessId != os.Getpid() {
t.Errorf("the debug process id on the file doesn't match, got %v, want %v", odoDebugFileData.DebugProcessId, os.Getpid())
}
if odoDebugFileData.ComponentName != tt.args.defaultPortForwarder.componentName {
t.Errorf("the component name on the file doesn't match, got %v, want %v", odoDebugFileData.ComponentName, tt.args.defaultPortForwarder.componentName)
}
if odoDebugFileData.AppName != tt.args.defaultPortForwarder.appName {
t.Errorf("the app name on the file doesn't match, got %v, want %v", odoDebugFileData.AppName, tt.args.defaultPortForwarder.appName)
}
if odoDebugFileData.AppName != tt.args.defaultPortForwarder.appName {
t.Errorf("the app name on the file doesn't match, got %v, want %v", odoDebugFileData.AppName, tt.args.defaultPortForwarder.appName)
}
if odoDebugFileData.ProjectName != tt.args.defaultPortForwarder.client.Namespace {
t.Errorf("the app name on the file doesn't match, got %v, want %v", odoDebugFileData.AppName, tt.args.defaultPortForwarder.client.Namespace)
}
})
}
}
2 changes: 2 additions & 0 deletions pkg/odo/cli/debug/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Debug allows you to remotely debug your application`
func NewCmdDebug(name, fullName string) *cobra.Command {

portforwardCmd := NewCmdPortForward(portforwardCommandName, util.GetFullName(fullName, portforwardCommandName))
infoCmd := NewCmdInfo(infoCommandName, util.GetFullName(fullName, infoCommandName))

debugCmd := &cobra.Command{
Use: name,
Expand All @@ -25,6 +26,7 @@ func NewCmdDebug(name, fullName string) *cobra.Command {

debugCmd.SetUsageTemplate(util.CmdUsageTemplate)
debugCmd.AddCommand(portforwardCmd)
debugCmd.AddCommand(infoCmd)
debugCmd.Annotations = map[string]string{"command": "main"}

return debugCmd
Expand Down
85 changes: 85 additions & 0 deletions pkg/odo/cli/debug/info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package debug

import (
"github.com/openshift/odo/pkg/config"
"github.com/openshift/odo/pkg/debug"
"github.com/openshift/odo/pkg/log"
"github.com/openshift/odo/pkg/odo/genericclioptions"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
k8sgenclioptions "k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)

// PortForwardOptions contains all the options for running the port-forward cli command.
type InfoOptions struct {
Namespace string
PortForwarder *debug.DefaultPortForwarder
*genericclioptions.Context
localConfigInfo *config.LocalConfigInfo
contextDir string
}

var (
infoLong = templates.LongDesc(`
Gets information regarding any debug session of the component.
`)

infoExample = templates.Examples(`
# Get information regarding any debug session of the component
odo debug info
`)
)

const (
infoCommandName = "info"
)

func NewInfoOptions() *InfoOptions {
return &InfoOptions{}
}

// Complete completes all the required options for port-forward cmd.
func (o *InfoOptions) Complete(name string, cmd *cobra.Command, args []string) (err error) {
o.Context = genericclioptions.NewContext(cmd)
cfg, err := config.NewLocalConfigInfo(o.contextDir)
o.localConfigInfo = cfg

// Using Discard streams because nothing important is logged
o.PortForwarder = debug.NewDefaultPortForwarder(cfg.GetName(), cfg.GetApplication(), o.Client, k8sgenclioptions.NewTestIOStreamsDiscard())

return err
}

// Validate validates all the required options for port-forward cmd.
func (o InfoOptions) Validate() error {
return nil
}

// Run implements all the necessary functionality for port-forward cmd.
func (o InfoOptions) Run() error {
if localPort, debugging := debug.GetDebugInfo(o.PortForwarder); debugging {
log.Infof("Debug is running for the component on the local port : %v\n", localPort)
} else {
log.Infof("Debug is not running for the component %v\n", o.localConfigInfo.GetName())
}
return nil
}

// NewCmdInfo implements the debug info odo command
func NewCmdInfo(name, fullName string) *cobra.Command {

opts := NewInfoOptions()
cmd := &cobra.Command{
Use: name,
Short: "Displays debug info of a component",
Long: infoLong,
Example: infoExample,
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(opts, cmd, args)
},
}
genericclioptions.AddContextFlag(cmd, &opts.contextDir)

return cmd
}
18 changes: 14 additions & 4 deletions pkg/odo/cli/debug/portforward.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package debug

import (
"fmt"
"os"
"os/signal"

"github.com/openshift/odo/pkg/config"
"github.com/openshift/odo/pkg/debug"
"github.com/openshift/odo/pkg/odo/genericclioptions"
"os"
"os/signal"
"syscall"

"github.com/spf13/cobra"

Expand Down Expand Up @@ -89,8 +89,13 @@ func (o PortForwardOptions) Validate() error {
func (o PortForwardOptions) Run() error {

signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt)
signal.Notify(signals, os.Interrupt,
syscall.SIGHUP,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT)
defer signal.Stop(signals)
defer os.RemoveAll(debug.GetDebugInfoFilePath(o.Client, o.localConfigInfo.GetName(), o.localConfigInfo.GetApplication()))

go func() {
<-signals
Expand All @@ -99,6 +104,11 @@ func (o PortForwardOptions) Run() error {
}
}()

err := debug.CreateDebugInfoFile(o.PortForwarder, o.PortPair)
if err != nil {
return err
}

return o.PortForwarder.ForwardPorts(o.PortPair, o.StopChannel, o.ReadyChannel)
}

Expand Down
Loading

0 comments on commit a2bae96

Please sign in to comment.