From 02f39efbe53c930521272f1e9dba8806eb5c7bd7 Mon Sep 17 00:00:00 2001 From: Andrey Trubachev <1354987+atrubachev@users.noreply.github.com> Date: Fri, 17 Apr 2020 15:36:30 +0200 Subject: [PATCH] Fix a race condition in tests --- errors/errors.go | 15 +- providers/dell/idrac8/actions_test.go | 205 ++++++--------- providers/dell/idrac9/actions_test.go | 205 ++++++--------- providers/dell/m1000e/actions_test.go | 349 +++++++++++++------------- providers/hp/c7000/actions.go | 10 +- providers/hp/c7000/actions_test.go | 323 ++++++++++++++---------- providers/hp/ilo/actions_test.go | 186 ++++++-------- sshmock/sshmock.go | 233 +++++++++-------- sshmock/sshmock_test.go | 19 +- 9 files changed, 737 insertions(+), 808 deletions(-) diff --git a/errors/errors.go b/errors/errors.go index cdc5a701..ccffb950 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -8,34 +8,45 @@ import ( var ( // ErrLoginFailed is returned when we fail to login to a bmc ErrLoginFailed = errors.New("failed to login") + // ErrBiosNotFound is returned when we are not able to find the server bios version ErrBiosNotFound = errors.New("bios version not found") + // ErrVendorUnknown is returned when we are unable to identify the redfish vendor ErrVendorUnknown = errors.New("unable to identify the vendor") + // ErrInvalidSerial is returned when the serial number for the device is invalid ErrInvalidSerial = errors.New("unable to find the serial number") + // ErrPageNotFound is used to inform the http request that we couldn't find the expected page and/or endpoint ErrPageNotFound = errors.New("requested page couldn't be found in the server") + // ErrRedFishNotSupported is returned when redfish isn't supported by the vendor ErrRedFishNotSupported = errors.New("redfish not supported") + // ErrUnableToReadData is returned when we fail to read data from a chassis or bmc ErrUnableToReadData = errors.New("unable to read data from this device") + // ErrVendorNotSupported is returned when we are able to identify a vendor but we won't support it ErrVendorNotSupported = errors.New("vendor not supported") + // ErrUnableToGetSessionToken is returned when we are unable to retrieve ST2 which is required to set configuration parameters ErrUnableToGetSessionToken = errors.New("unable to get ST2 session token") + // Err500 is returned when we receive a 500 response from an endpoint. Err500 = errors.New("we've received 500 calling this endpoint") + // ErrNotImplemented is returned for not implemented methods called ErrNotImplemented = errors.New("this feature hasn't been implemented yet") + // ErrFeatureUnavailable is returned for features not available/supported. ErrFeatureUnavailable = errors.New("this feature isn't supported/available for this hardware") // ErrIdracMaxSessionsReached indicates the bmc has reached the max number of login sessions. - ErrIdracMaxSessionsReached = errors.New("The maximum number of user sessions is reached") + ErrIdracMaxSessionsReached = errors.New("the maximum number of user sessions is reached") // Err401Redfish indicates auth failure - Err401Redfish = errors.New("Redfish authorization failed") + Err401Redfish = errors.New("redfish authorization failed") // ErrDeviceNotMatched is the error returned when the device was not a type it was probed for ErrDeviceNotMatched = errors.New("the vendor device did not match the probe") diff --git a/providers/dell/idrac8/actions_test.go b/providers/dell/idrac8/actions_test.go index f88baaaa..3a9f9bfc 100644 --- a/providers/dell/idrac8/actions_test.go +++ b/providers/dell/idrac8/actions_test.go @@ -6,8 +6,12 @@ import ( "github.com/bmc-toolbox/bmclib/sshmock" ) +const ( + sshUsername = "super" + sshPassword = "test" +) + var ( - sshServer *sshmock.Server sshAnswers = map[string][]byte{ "racadm serveraction hardreset": []byte(`Server power operation successful`), "racadm racreset hard": []byte(`RAC reset operation initiated successfully. It may take a few @@ -39,144 +43,87 @@ var ( } ) -func setupSSH() (bmc *IDrac8, err error) { - username := "super" - password := "test" - - sshServer, err = sshmock.New(sshAnswers, true) +func setupBMC() (func(), *IDrac8, error) { + ssh, err := sshmock.New(sshAnswers) if err != nil { - return bmc, err + return nil, nil, err } - address := sshServer.Address() - - bmc, err = New(address, username, password) + tearDown, address, err := ssh.ListenAndServe() if err != nil { - return bmc, err + return nil, nil, err } - return bmc, err -} - -func tearDownSSH() { - sshServer.Close() -} - -func TestIDracPowerCycle(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() + bmc, err := New(address, sshUsername, sshPassword) if err != nil { - t.Fatalf("Found errors during the test setup %v", err) + tearDown() + return nil, nil, err } - answer, err := bmc.PowerCycle() - if err != nil { - t.Fatalf("Found errors calling bmc.PowerCycle %v", err) - } - - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) - } - - tearDownSSH() + return tearDown, bmc, err } -func TestIDracPowerCycleBmc(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() +func Test_IDrac8(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { - t.Fatalf("Found errors during the test setup %v", err) + t.Fatalf("failed to setup BMC: %v", err) + } + defer tearDown() + + tests := []struct { + name string + bmcMethod func() (bool, error) + want bool + wantErr bool + }{ + { + name: "PowerCycle", + bmcMethod: bmc.PowerCycle, + want: true, + wantErr: false, + }, + { + name: "PowerCycleBmc", + bmcMethod: bmc.PowerCycleBmc, + want: true, + wantErr: false, + }, + { + name: "PowerOn", + bmcMethod: bmc.PowerOn, + want: true, + wantErr: false, + }, + { + name: "PowerOff", + bmcMethod: bmc.PowerOff, + want: true, + wantErr: false, + }, + { + name: "PxeOnce", + bmcMethod: bmc.PxeOnce, + want: true, + wantErr: false, + }, + { + name: "IsOn", + bmcMethod: bmc.IsOn, + want: true, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.bmcMethod() + + if (err != nil) != tt.wantErr { + t.Errorf("error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("got = %v, want %v", got, tt.want) + } + }) } - - answer, err := bmc.PowerCycleBmc() - if err != nil { - t.Fatalf("Found errors calling bmc.PowerCycleBmc %v", err) - } - - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) - } - - tearDownSSH() -} - -func TestIDracPowerOn(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() - if err != nil { - t.Fatalf("Found errors during the test setup %v", err) - } - - answer, err := bmc.PowerOn() - if err != nil { - t.Fatalf("Found errors calling bmc.PowerOn %v", err) - } - - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) - } - - tearDownSSH() -} - -func TestIDracPowerOff(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() - if err != nil { - t.Fatalf("Found errors during the test setup %v", err) - } - - answer, err := bmc.PowerOff() - if err != nil { - t.Fatalf("Found errors calling bmc.PowerOff %v", err) - } - - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) - } - - tearDownSSH() -} - -func TestIDracPxeOnce(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() - if err != nil { - t.Fatalf("Found errors during the test setup %v", err) - } - - answer, err := bmc.PxeOnce() - if err != nil { - t.Fatalf("Found errors calling bmc.PxeOnce %v", err) - } - - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) - } - - tearDownSSH() -} - -func TestIDracIsOn(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() - if err != nil { - t.Fatalf("Found errors during the test setup %v", err) - } - - answer, err := bmc.IsOn() - if err != nil { - t.Fatalf("Found errors calling bmc.IsOn %v", err) - } - - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) - } - - tearDownSSH() } diff --git a/providers/dell/idrac9/actions_test.go b/providers/dell/idrac9/actions_test.go index e6724220..a7a323d6 100644 --- a/providers/dell/idrac9/actions_test.go +++ b/providers/dell/idrac9/actions_test.go @@ -6,8 +6,12 @@ import ( "github.com/bmc-toolbox/bmclib/sshmock" ) +const ( + sshUsername = "super" + sshPassword = "test" +) + var ( - sshServer *sshmock.Server sshAnswers = map[string][]byte{ "racadm serveraction hardreset": []byte(`Server power operation successful`), "racadm racreset hard": []byte(`RAC reset operation initiated successfully. It may take a few @@ -39,144 +43,87 @@ var ( } ) -func setupSSH() (bmc *IDrac9, err error) { - username := "super" - password := "test" - - sshServer, err = sshmock.New(sshAnswers, true) +func setupBMC() (func(), *IDrac9, error) { + ssh, err := sshmock.New(sshAnswers) if err != nil { - return bmc, err + return nil, nil, err } - address := sshServer.Address() - - bmc, err = New(address, username, password) + tearDown, address, err := ssh.ListenAndServe() if err != nil { - return bmc, err + return nil, nil, err } - return bmc, err -} - -func tearDownSSH() { - sshServer.Close() -} - -func TestIDracPowerCycle(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() + bmc, err := New(address, sshUsername, sshPassword) if err != nil { - t.Fatalf("Found errors during the test setup %v", err) + tearDown() + return nil, nil, err } - answer, err := bmc.PowerCycle() - if err != nil { - t.Fatalf("Found errors calling bmc.PowerCycle %v", err) - } - - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) - } - - tearDownSSH() + return tearDown, bmc, err } -func TestIDracPowerCycleBmc(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() +func Test_IDrac9(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { - t.Fatalf("Found errors during the test setup %v", err) + t.Fatalf("failed to setup BMC: %v", err) + } + defer tearDown() + + tests := []struct { + name string + bmcMethod func() (bool, error) + want bool + wantErr bool + }{ + { + name: "PowerCycle", + bmcMethod: bmc.PowerCycle, + want: true, + wantErr: false, + }, + { + name: "PowerCycleBmc", + bmcMethod: bmc.PowerCycleBmc, + want: true, + wantErr: false, + }, + { + name: "PowerOn", + bmcMethod: bmc.PowerOn, + want: true, + wantErr: false, + }, + { + name: "PowerOff", + bmcMethod: bmc.PowerOff, + want: true, + wantErr: false, + }, + { + name: "PxeOnce", + bmcMethod: bmc.PxeOnce, + want: true, + wantErr: false, + }, + { + name: "IsOn", + bmcMethod: bmc.IsOn, + want: true, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.bmcMethod() + + if (err != nil) != tt.wantErr { + t.Errorf("error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("got = %v, want %v", got, tt.want) + } + }) } - - answer, err := bmc.PowerCycleBmc() - if err != nil { - t.Fatalf("Found errors calling bmc.PowerCycleBmc %v", err) - } - - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) - } - - tearDownSSH() -} - -func TestIDracPowerOn(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() - if err != nil { - t.Fatalf("Found errors during the test setup %v", err) - } - - answer, err := bmc.PowerOn() - if err != nil { - t.Fatalf("Found errors calling bmc.PowerOn %v", err) - } - - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) - } - - tearDownSSH() -} - -func TestIDracPowerOff(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() - if err != nil { - t.Fatalf("Found errors during the test setup %v", err) - } - - answer, err := bmc.PowerOff() - if err != nil { - t.Fatalf("Found errors calling bmc.PowerOff %v", err) - } - - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) - } - - tearDownSSH() -} - -func TestIDracPxeOnce(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() - if err != nil { - t.Fatalf("Found errors during the test setup %v", err) - } - - answer, err := bmc.PxeOnce() - if err != nil { - t.Fatalf("Found errors calling bmc.PxeOnce %v", err) - } - - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) - } - - tearDownSSH() -} - -func TestIDracIsOn(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() - if err != nil { - t.Fatalf("Found errors during the test setup %v", err) - } - - answer, err := bmc.IsOn() - if err != nil { - t.Fatalf("Found errors calling bmc.IsOn %v", err) - } - - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) - } - - tearDownSSH() } diff --git a/providers/dell/m1000e/actions_test.go b/providers/dell/m1000e/actions_test.go index 97bcfc24..ddbdb123 100644 --- a/providers/dell/m1000e/actions_test.go +++ b/providers/dell/m1000e/actions_test.go @@ -6,8 +6,12 @@ import ( "github.com/bmc-toolbox/bmclib/sshmock" ) +const ( + sshUsername = "super" + sshPassword = "test" +) + var ( - sshServer *sshmock.Server sshAnswers = map[string][]byte{ "racadm racreset": []byte(`CMC reset operation initiated successfully. It may take up to a minute for the CMC to come back online again. @@ -106,308 +110,295 @@ var ( } ) -func setupSSH() (bmc *M1000e, err error) { - username := "super" - password := "test" - - sshServer, err = sshmock.New(sshAnswers, true) +func setupBMC() (func(), *M1000e, error) { + ssh, err := sshmock.New(sshAnswers) if err != nil { - return bmc, err + return nil, nil, err } - address := sshServer.Address() - - bmc, err = New(address, username, password) + tearDown, address, err := ssh.ListenAndServe() if err != nil { - return bmc, err + return nil, nil, err } - return bmc, err -} - -func tearDownSSH() { - sshServer.Close() -} - -func TestChassisPowerCycle(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() + bmc, err := New(address, sshUsername, sshPassword) if err != nil { - t.Fatalf("Found errors during the test setup %v", err) + tearDown() + return nil, nil, err } - defer tearDownSSH() - answer, err := bmc.PowerCycle() - if err != nil { - t.Fatalf("Found errors calling bmc.PowerCycle %v", err) - } + return tearDown, bmc, err +} - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) +func Test_chassisBMC(t *testing.T) { + tearDown, bmc, err := setupBMC() + if err != nil { + t.Fatalf("failed to setup BMC: %v", err) + } + defer tearDown() + + tests := []struct { + name string + bmcMethod func() (bool, error) + want bool + wantErr bool + }{ + { + name: "PowerCycle", + bmcMethod: bmc.PowerCycle, + want: true, + wantErr: false, + }, + { + name: "PowerOn", + bmcMethod: bmc.PowerOn, + want: true, + wantErr: false, + }, + { + name: "PowerOff", + bmcMethod: bmc.PowerOff, + want: true, + wantErr: false, + }, + { + name: "IsOn", + bmcMethod: bmc.IsOn, + want: true, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.bmcMethod() + + if (err != nil) != tt.wantErr { + t.Errorf("error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("got = %v, want %v", got, tt.want) + } + }) } } -func TestChassisPowerOn(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() +func Test_FindBladePosition(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { t.Fatalf("Found errors during the test setup %v", err) } - defer tearDownSSH() + defer tearDown() - answer, err := bmc.PowerOn() - if err != nil { - t.Fatalf("Found errors calling bmc.PowerOn %v", err) - } + want, wantErr := 2, false - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) - } -} - -func TestChassisPowerOff(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() - if err != nil { - t.Fatalf("Found errors during the test setup %v", err) - } - defer tearDownSSH() + got, err := bmc.FindBladePosition("74XXX72") - answer, err := bmc.PowerOff() if err != nil { - t.Fatalf("Found errors calling bmc.PowerOff %v", err) + t.Errorf("error = %v, wantErr %v", got, wantErr) } - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + if got != want { + t.Errorf("got = %v, want %v", got, want) } } -func TestChassisIsOn(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() +func Test_PowerCycleBlade(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { t.Fatalf("Found errors during the test setup %v", err) } - defer tearDownSSH() - - answer, err := bmc.IsOn() - if err != nil { - t.Fatalf("Found errors calling bmc.IsOn %v", err) - } + defer tearDown() - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) - } -} -func TestChassisFindBladePosition(t *testing.T) { - expectedAnswer := 2 + want, wantErr := true, false - bmc, err := setupSSH() - if err != nil { - t.Fatalf("Found errors during the test setup %v", err) - } - defer tearDownSSH() + got, err := bmc.PowerCycleBlade(2) - answer, err := bmc.FindBladePosition("74XXX72") if err != nil { - t.Fatalf("Found errors calling bmc.FindBladePosition %v", err) + t.Errorf("error = %v, wantErr %v", got, wantErr) } - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + if got != want { + t.Errorf("got = %v, want %v", got, want) } } -func TestChassisPowerCycleBlade(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() +func Test_ReseatBlade(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { t.Fatalf("Found errors during the test setup %v", err) } - defer tearDownSSH() + defer tearDown() - answer, err := bmc.PowerCycleBlade(2) - if err != nil { - t.Fatalf("Found errors calling bmc.PowerCycleBlade %v", err) - } - - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) - } -} + want, wantErr := true, false -func TestChassisReseatBlade(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() - if err != nil { - t.Fatalf("Found errors during the test setup %v", err) - } - defer tearDownSSH() + got, err := bmc.ReseatBlade(2) - answer, err := bmc.ReseatBlade(2) if err != nil { - t.Fatalf("Found errors calling bmc.ReseatBlade %v", err) + t.Errorf("error = %v, wantErr %v", got, wantErr) } - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + if got != want { + t.Errorf("got = %v, want %v", got, want) } } -func TestChassisPowerOnBlade(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() +func Test_PowerOnBlade(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { t.Fatalf("Found errors during the test setup %v", err) } - defer tearDownSSH() + defer tearDown() + + want, wantErr := true, false + + got, err := bmc.PowerOnBlade(2) - answer, err := bmc.PowerOnBlade(2) if err != nil { - t.Fatalf("Found errors calling bmc.PowerOnBlade %v", err) + t.Errorf("error = %v, wantErr %v", got, wantErr) } - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + if got != want { + t.Errorf("got = %v, want %v", got, want) } } -func TestChassisPowerOffBlade(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() +func Test_PowerOffBlade(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { t.Fatalf("Found errors during the test setup %v", err) } - defer tearDownSSH() + defer tearDown() + + want, wantErr := true, false + + got, err := bmc.PowerOffBlade(2) - answer, err := bmc.PowerOffBlade(2) if err != nil { - t.Fatalf("Found errors calling bmc.PowerOffBlade %v", err) + t.Errorf("error = %v, wantErr %v", got, wantErr) } - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + if got != want { + t.Errorf("got = %v, want %v", got, want) } } -func TestChassisIsOnBlade(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() +func Test_IsOnBlade(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { t.Fatalf("Found errors during the test setup %v", err) } - defer tearDownSSH() + defer tearDown() + + want, wantErr := true, false + + got, err := bmc.IsOnBlade(2) - answer, err := bmc.IsOnBlade(2) if err != nil { - t.Fatalf("Found errors calling bmc.IsOnBlade %v", err) + t.Errorf("error = %v, wantErr %v", got, wantErr) } - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + if got != want { + t.Errorf("got = %v, want %v", got, want) } } -func TestChassisPowerCycleBmcBlade(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() +func Test_PowerCycleBmcBlade(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { t.Fatalf("Found errors during the test setup %v", err) } - defer tearDownSSH() + defer tearDown() + + want, wantErr := true, false + + got, err := bmc.PowerCycleBmcBlade(2) - answer, err := bmc.PowerCycleBmcBlade(2) if err != nil { - t.Fatalf("Found errors calling bmc.PowerCycleBmcBlade %v", err) + t.Errorf("error = %v, wantErr %v", got, wantErr) } - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + if got != want { + t.Errorf("got = %v, want %v", got, want) } } -func TestChassisPxeOnceBlade(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() +func Test_PxeOnceBlade(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { t.Fatalf("Found errors during the test setup %v", err) } - defer tearDownSSH() + defer tearDown() + + want, wantErr := true, false + + got, err := bmc.PxeOnceBlade(2) - answer, err := bmc.PxeOnceBlade(2) if err != nil { - t.Fatalf("Found errors calling bmc.PxeOnceBlade %v", err) + t.Errorf("error = %v, wantErr %v", got, wantErr) } - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + if got != want { + t.Errorf("got = %v, want %v", got, want) } } -func TestChassisSetIpmiOverLan(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() +func Test_SetIpmiOverLan(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { t.Fatalf("Found errors during the test setup %v", err) } - defer tearDownSSH() + defer tearDown() + + want, wantErr := true, false + + got, err := bmc.SetIpmiOverLan(2, true) - answer, err := bmc.SetIpmiOverLan(2, true) if err != nil { - t.Fatalf("Found errors calling bmc.PxeOnceBlade %v", err) + t.Errorf("error = %v, wantErr %v", got, wantErr) } - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + if got != want { + t.Errorf("got = %v, want %v", got, want) } } -func TestChassisSetDynamicPower(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() +func Test_SetDynamicPower(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { t.Fatalf("Found errors during the test setup %v", err) } - defer tearDownSSH() + defer tearDown() + + want, wantErr := true, false + + got, err := bmc.SetDynamicPower(true) - answer, err := bmc.SetDynamicPower(true) if err != nil { - t.Fatalf("Found errors calling bmc.SetDynamicPower %v", err) + t.Errorf("error = %v, wantErr %v", got, wantErr) } - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + if got != want { + t.Errorf("got = %v, want %v", got, want) } } -func TestChassisSetFlexAddressState(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() +func Test_SetFlexAddressState(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { t.Fatalf("Found errors during the test setup %v", err) } - defer tearDownSSH() + defer tearDown() + + want, wantErr := true, false + + got, err := bmc.SetFlexAddressState(1, false) - answer, err := bmc.SetFlexAddressState(1, false) if err != nil { - t.Fatalf("Found errors calling bmc.SetFlexAddressState %v", err) + t.Errorf("error = %v, wantErr %v", got, wantErr) } - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + if got != want { + t.Errorf("got = %v, want %v", got, want) } } diff --git a/providers/hp/c7000/actions.go b/providers/hp/c7000/actions.go index 2f8f6409..1f7985db 100644 --- a/providers/hp/c7000/actions.go +++ b/providers/hp/c7000/actions.go @@ -29,12 +29,12 @@ func (c *C7000) PowerCycle() (status bool, err error) { // PowerOn power on the chassis func (c *C7000) PowerOn() (status bool, err error) { - return status, fmt.Errorf("Unsupported action") + return status, errors.ErrFeatureUnavailable } // PowerOff power off the chassis func (c *C7000) PowerOff() (status bool, err error) { - return status, fmt.Errorf("Unsupported action") + return status, errors.ErrFeatureUnavailable } // IsOn tells if a machine is currently powered on @@ -62,7 +62,7 @@ func (c *C7000) FindBladePosition(serial string) (position int, err error) { return position, err } - for _, line := range strings.Split(string(output), "\n") { + for _, line := range strings.Split(output, "\n") { line = strings.Replace(line, "Server-", "", -1) data := strings.FieldsFunc(line, sshclient.IsntLetterOrNumber) for _, field := range data { @@ -382,11 +382,11 @@ end_marker` // SetFlexAddressState Enable/Disable FlexAddress disables flex Addresses for blades // FlexAddress is a virtual addressing scheme -func (c *C7000) SetFlexAddressState(position int, enable bool) (status bool, err error) { +func (c *C7000) SetFlexAddressState(_ int, _ bool) (status bool, err error) { return status, errors.ErrNotImplemented } // SetIpmiOverLan Enable/Disable IPMI over lan parameter per blade in chassis -func (c *C7000) SetIpmiOverLan(position int, enable bool) (status bool, err error) { +func (c *C7000) SetIpmiOverLan(_ int, _ bool) (status bool, err error) { return status, errors.ErrNotImplemented } diff --git a/providers/hp/c7000/actions_test.go b/providers/hp/c7000/actions_test.go index 91a2ffb1..fdcc999d 100644 --- a/providers/hp/c7000/actions_test.go +++ b/providers/hp/c7000/actions_test.go @@ -1,29 +1,20 @@ package c7000 import ( - "time" - "github.com/bmc-toolbox/bmclib/sshmock" - mrand "math/rand" - - "fmt" "testing" ) // Test server based on: // http://grokbase.com/t/gg/golang-nuts/165yek1eje/go-nuts-creating-an-ssh-server-instance-for-tests -func init() { - mrand.Seed(time.Now().Unix()) -} - -func sshServerAddress(min, max int) string { - return fmt.Sprintf("127.0.0.1:%d", mrand.Intn(max-min)+min) -} +const ( + sshUsername = "super" + sshPassword = "test" +) var ( - sshServer *sshmock.Server sshAnswers = map[string][]byte{ "RESTART OA ACTIVE": []byte(`Restarting Onboard Administrator in bay`), "SHOW SERVER NAMES": []byte(` @@ -104,232 +95,296 @@ var ( } ) -func setupSSH() (bmc *C7000, err error) { - sshServer, err = sshmock.New(sshAnswers, true) +func setupBMC() (func(), *C7000, error) { + ssh, err := sshmock.New(sshAnswers) if err != nil { - return bmc, err + return nil, nil, err } - address := sshServer.Address() - - bmc, err = setup() + tearDown, address, err := ssh.ListenAndServe() if err != nil { - return bmc, err + return nil, nil, err } - bmc.ip = address - return bmc, err -} + bmc := &C7000{ + ip: address, + username: sshUsername, + password: sshPassword, + } -func tearDownSSH() { - tearDown() - sshServer.Close() + return tearDown, bmc, err } -func TestChassisPowerCycle(t *testing.T) { - expectedAnswer := true +func Test_chassisBMC(t *testing.T) { + tearDown, bmc, err := setupBMC() + if err != nil { + t.Fatalf("failed to setup BMC: %v", err) + } + defer tearDown() + + tests := []struct { + name string + bmcMethod func() (bool, error) + want bool + wantErr bool + }{ + { + name: "PowerCycle", + bmcMethod: bmc.PowerCycle, + want: true, + wantErr: false, + }, + { + name: "PowerOn", + bmcMethod: bmc.PowerOn, + want: false, + wantErr: true, + }, + { + name: "PowerOff", + bmcMethod: bmc.PowerOff, + want: false, + wantErr: true, + }, + { + name: "IsOn", + bmcMethod: bmc.IsOn, + want: true, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.bmcMethod() + + if (err != nil) != tt.wantErr { + t.Errorf("error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("got = %v, want %v", got, tt.want) + } + }) + } +} - bmc, err := setupSSH() +func Test_FindBladePosition(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { t.Fatalf("Found errors during the test setup %v", err) } - defer tearDownSSH() + defer tearDown() + + want, wantErr := 1, false + + got, err := bmc.FindBladePosition("CZXXXXXXEK") - answer, err := bmc.PowerCycle() if err != nil { - t.Fatalf("Found errors calling bmc.PowerCycle %v", err) + t.Errorf("error = %v, wantErr %v", got, wantErr) } - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + if (err != nil) != wantErr { + t.Errorf("got = %v, want %v", got, want) } } -func TestChassisIsOn(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() +func Test_PowerCycleBlade(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { t.Fatalf("Found errors during the test setup %v", err) } - defer tearDownSSH() + defer tearDown() - answer, err := bmc.IsOn() - if err != nil { - t.Fatalf("Found errors calling bmc.IsOn %v", err) + want, wantErr := true, false + + got, err := bmc.PowerCycleBlade(1) + + if (err != nil) != wantErr { + t.Errorf("error = %v, wantErr %v", got, wantErr) } - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + if got != want { + t.Errorf("got = %v, want %v", got, want) } } -func TestChassisFindBladePosition(t *testing.T) { - expectedAnswer := 1 - - bmc, err := setupSSH() +func Test_ReseatBlade(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { t.Fatalf("Found errors during the test setup %v", err) } - defer tearDownSSH() + defer tearDown() - answer, err := bmc.FindBladePosition("CZXXXXXXEK") - if err != nil { - t.Fatalf("Found errors calling bmc.FindBladePosition %v", err) + want, wantErr := true, false + + got, err := bmc.ReseatBlade(1) + + if (err != nil) != wantErr { + t.Errorf("error = %v, wantErr %v", got, wantErr) } - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + if got != want { + t.Errorf("got = %v, want %v", got, want) } } -func TestChassisPowerCycleBlade(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() +func Test_PowerOnBlade(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { t.Fatalf("Found errors during the test setup %v", err) } - defer tearDownSSH() + defer tearDown() - answer, err := bmc.PowerCycleBlade(1) - if err != nil { - t.Fatalf("Found errors calling bmc.PowerCycleBlade %v", err) + want, wantErr := true, false + + got, err := bmc.PowerOnBlade(1) + + if (err != nil) != wantErr { + t.Errorf("error = %v, wantErr %v", got, wantErr) } - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + if got != want { + t.Errorf("got = %v, want %v", got, want) } } -func TestChassisReseatBlade(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() +func Test_PowerOffBlade(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { t.Fatalf("Found errors during the test setup %v", err) } - defer tearDownSSH() + defer tearDown() - answer, err := bmc.ReseatBlade(1) - if err != nil { - t.Fatalf("Found errors calling bmc.ReseatBlade %v", err) + want, wantErr := true, false + + got, err := bmc.PowerOffBlade(1) + + if (err != nil) != wantErr { + t.Errorf("error = %v, wantErr %v", got, wantErr) } - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + if got != want { + t.Errorf("got = %v, want %v", got, want) } } -func TestChassisPowerOnBlade(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() +func Test_IsOnBlade(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { t.Fatalf("Found errors during the test setup %v", err) } - defer tearDownSSH() + defer tearDown() - answer, err := bmc.PowerOnBlade(1) - if err != nil { - t.Fatalf("Found errors calling bmc.PowerOnBlade %v", err) + want, wantErr := true, false + + got, err := bmc.IsOnBlade(1) + + if (err != nil) != wantErr { + t.Errorf("error = %v, wantErr %v", got, wantErr) } - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + if got != want { + t.Errorf("got = %v, want %v", got, want) } } -func TestChassisPowerOffBlade(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() +func Test_PowerCycleBmcBlade(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { t.Fatalf("Found errors during the test setup %v", err) } - defer tearDownSSH() + defer tearDown() - answer, err := bmc.PowerOffBlade(1) - if err != nil { - t.Fatalf("Found errors calling bmc.PowerOffBlade %v", err) + want, wantErr := true, false + + got, err := bmc.PowerCycleBmcBlade(1) + + if (err != nil) != wantErr { + t.Errorf("error = %v, wantErr %v", got, wantErr) } - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + if got != want { + t.Errorf("got = %v, want %v", got, want) } } -func TestChassisIsOnBlade(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() +func Test_PxeOnceBlade(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { t.Fatalf("Found errors during the test setup %v", err) } - defer tearDownSSH() + defer tearDown() - answer, err := bmc.IsOnBlade(1) - if err != nil { - t.Fatalf("Found errors calling bmc.IsOnBlade %v", err) + want, wantErr := true, false + + got, err := bmc.PxeOnceBlade(1) + + if (err != nil) != wantErr { + t.Errorf("error = %v, wantErr %v", got, wantErr) } - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + if got != want { + t.Errorf("got = %v, want %v", got, want) } } -func TestChassisPowerCycleBmcBlade(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() +func Test_SetIpmiOverLan(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { t.Fatalf("Found errors during the test setup %v", err) } - defer tearDownSSH() + defer tearDown() - answer, err := bmc.PowerCycleBmcBlade(1) - if err != nil { - t.Fatalf("Found errors calling bmc.PowerCycleBmcBlade %v", err) + want, wantErr := false, true + + got, err := bmc.SetIpmiOverLan(1, true) + + if (err != nil) != wantErr { + t.Errorf("error = %v, wantErr %v", got, wantErr) } - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + if got != want { + t.Errorf("got = %v, want %v", got, want) } } -func TestChassisPxeOnceBlade(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() +func Test_SetDynamicPower(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { t.Fatalf("Found errors during the test setup %v", err) } - defer tearDownSSH() + defer tearDown() - answer, err := bmc.PxeOnceBlade(1) - if err != nil { - t.Fatalf("Found errors calling bmc.PxeOnceBlade %v", err) + want, wantErr := true, false + + got, err := bmc.SetDynamicPower(false) + + if (err != nil) != wantErr { + t.Errorf("error = %v, wantErr %v", got, wantErr) } - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + if got != want { + t.Errorf("got = %v, want %v", got, want) } } -func TestChassisSetDynamicPower(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() +func Test_SetFlexAddressState(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { t.Fatalf("Found errors during the test setup %v", err) } - defer tearDownSSH() + defer tearDown() - answer, err := bmc.SetDynamicPower(false) - if err != nil { - t.Fatalf("Found errors calling bmc.SetDynamicPower %v", err) + want, wantErr := false, true + + got, err := bmc.SetFlexAddressState(1, false) + + if (err != nil) != wantErr { + t.Errorf("error = %v, wantErr %v", got, wantErr) } - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + if got != want { + t.Errorf("got = %v, want %v", got, want) } } + diff --git a/providers/hp/ilo/actions_test.go b/providers/hp/ilo/actions_test.go index 4c3f886e..ee59f556 100644 --- a/providers/hp/ilo/actions_test.go +++ b/providers/hp/ilo/actions_test.go @@ -1,29 +1,20 @@ package ilo import ( - "time" - "github.com/bmc-toolbox/bmclib/sshmock" - mrand "math/rand" - - "fmt" "testing" ) // Test server based on: // http://grokbase.com/t/gg/golang-nuts/165yek1eje/go-nuts-creating-an-ssh-server-instance-for-tests -func init() { - mrand.Seed(time.Now().Unix()) -} - -func sshServerAddress(min, max int) string { - return fmt.Sprintf("127.0.0.1:%d", mrand.Intn(max-min)+min) -} +const ( + sshUsername = "super" + sshPassword = "test" +) var ( - sshServer *sshmock.Server sshAnswers = map[string][]byte{ "power reset": []byte(`Server resetting .......`), "reset /map1": []byte(`Resetting iLO`), @@ -33,118 +24,81 @@ var ( } ) -func setupSSH() (bmc *Ilo, err error) { - sshServer, err = sshmock.New(sshAnswers, true) +func setupBMC() (func(), *Ilo, error) { + ssh, err := sshmock.New(sshAnswers) if err != nil { - return bmc, err + return nil, nil, err } - address := sshServer.Address() - - bmc, err = setup() + tearDown, address, err := ssh.ListenAndServe() if err != nil { - return bmc, err + return nil, nil, err } - bmc.ip = address - return bmc, err -} - -func tearDownSSH() { - tearDown() - sshServer.Close() -} - -func TestIloPowerCycle(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() - if err != nil { - t.Fatalf("Found errors during the test setup %v", err) + bmc := &Ilo{ + ip: address, + username: sshUsername, + password: sshPassword, } - defer tearDownSSH() - answer, err := bmc.PowerCycle() - if err != nil { - t.Fatalf("Found errors calling bmc.PowerCycle %v", err) - } - - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) - } + return tearDown, bmc, err } -func TestIloPowerCycleBmc(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() - if err != nil { - t.Fatalf("Found errors during the test setup %v", err) - } - defer tearDownSSH() - - answer, err := bmc.PowerCycleBmc() +func Test_ilo(t *testing.T) { + tearDown, bmc, err := setupBMC() if err != nil { - t.Fatalf("Found errors calling bmc.PowerCycleBmc %v", err) - } - - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) - } -} - -func TestIloPowerOn(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() - if err != nil { - t.Fatalf("Found errors during the test setup %v", err) - } - defer tearDownSSH() - - answer, err := bmc.PowerOn() - if err != nil { - t.Fatalf("Found errors calling bmc.PowerOn %v", err) - } - - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) - } -} - -func TestIloPowerOff(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() - if err != nil { - t.Fatalf("Found errors during the test setup %v", err) - } - defer tearDownSSH() - - answer, err := bmc.PowerOff() - if err != nil { - t.Fatalf("Found errors calling bmc.PowerOff %v", err) - } - - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) - } -} - -func TestIloIsOn(t *testing.T) { - expectedAnswer := true - - bmc, err := setupSSH() - if err != nil { - t.Fatalf("Found errors during the test setup %v", err) - } - defer tearDownSSH() - - answer, err := bmc.IsOn() - if err != nil { - t.Fatalf("Found errors calling bmc.IsOn %v", err) - } - - if answer != expectedAnswer { - t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) + t.Fatalf("failed to setup BMC: %v", err) + } + defer tearDown() + + tests := []struct { + name string + bmcMethod func() (bool, error) + want bool + wantErr bool + }{ + { + name: "PowerCycle", + bmcMethod: bmc.PowerCycle, + want: true, + wantErr: false, + }, + { + name: "PowerCycleBmc", + bmcMethod: bmc.PowerCycleBmc, + want: true, + wantErr: false, + }, + { + name: "PowerOn", + bmcMethod: bmc.PowerOn, + want: true, + wantErr: false, + }, + { + name: "PowerOff", + bmcMethod: bmc.PowerOff, + want: true, + wantErr: false, + }, + { + name: "IsOn", + bmcMethod: bmc.IsOn, + want: true, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.bmcMethod() + + if (err != nil) != tt.wantErr { + t.Errorf("error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("got = %v, want %v", got, tt.want) + } + }) } } diff --git a/sshmock/sshmock.go b/sshmock/sshmock.go index c1f73002..b1163f4d 100644 --- a/sshmock/sshmock.go +++ b/sshmock/sshmock.go @@ -16,107 +16,83 @@ import ( "golang.org/x/crypto/ssh" ) +const ( + privateKeyBitSize = 2048 + maxAttemptsToCreateListener = 10 +) + +// Server is the basic struct for the sshMock server +type Server struct { + config *ssh.ServerConfig + answers map[string][]byte +} + // Test server based on: // http://grokbase.com/t/gg/golang-nuts/165yek1eje/go-nuts-creating-an-ssh-server-instance-for-tests // New creates a new sshmock instance -func New(answers map[string][]byte, randomPort bool) (s *Server, err error) { - mrand.Seed(time.Now().Unix()) - +func New(answers map[string][]byte) (*Server, error) { config := &ssh.ServerConfig{ PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { return nil, nil }, } - port := 22 - if randomPort { - port = mrand.Intn(20000-2000) + 2000 + privateKey, err := generateHostKey() + if err != nil { + return nil, err } - s = &Server{ - address: fmt.Sprintf("127.0.0.1:%d", port), - wait: make(chan interface{}), + config.AddHostKey(privateKey) + + server := &Server{ + config: config, answers: answers, } - key, err := s.generatePrivateKey(2048) - if err != nil { - return s, fmt.Errorf("failed to load private key: %s", err.Error()) - } + return server, err +} - private, err := ssh.ParsePrivateKey(s.encodePrivateKeyToPEM(key)) +func (s *Server) ListenAndServe() (func(), string, error) { + listener, err := createListener() if err != nil { - return s, fmt.Errorf("failed to parse private key: %s", err.Error()) + return nil, "", err } - config.AddHostKey(private) + addr := listener.Addr().String() + shutdown := func() { listener.Close() } - go s.run(config) - <-s.wait + go s.run(listener) - return s, err + return shutdown, addr, nil } -// Server is the basic struct for the sshMock server -type Server struct { - address string - ssh net.Listener - answers map[string][]byte - wait chan interface{} -} - -// Address returns the current sshmock server address -func (s *Server) Address() string { - return s.address -} - -func (s *Server) encodePrivateKeyToPEM(pk *rsa.PrivateKey) (payload []byte) { - block := pem.Block{ - Type: "RSA PRIVATE KEY", - Headers: nil, - Bytes: x509.MarshalPKCS1PrivateKey(pk), - } - return pem.EncodeToMemory(&block) -} - -func (s *Server) generatePrivateKey(bitSize int) (pk *rsa.PrivateKey, err error) { - pk, err = rsa.GenerateKey(rand.Reader, bitSize) - if err != nil { - return pk, err - } +func (s *Server) run(listener net.Listener) { + for { + conn, err := listener.Accept() + if err != nil { + log.Printf("failed to accept: %v", err) + continue + } - err = pk.Validate() - if err != nil { - return pk, err + if err := s.handleConnection(conn); err != nil { + log.Printf("failed to handle connection: %v", err) + } } - - return pk, err } -func (s *Server) run(config *ssh.ServerConfig) { - sshServer, err := net.Listen("tcp", s.address) +func (s *Server) handleConnection(conn net.Conn) error { + defer conn.Close() + serverConn, chans, reqs, err := ssh.NewServerConn(conn, s.config) if err != nil { - log.Fatalf("Failed to listen on %s (%s)", s.address, err) + return err } - s.ssh = sshServer + defer serverConn.Close() - close(s.wait) - for { - conn, err := s.ssh.Accept() - if err != nil { - break - } - - _, chans, reqs, err := ssh.NewServerConn(conn, config) - if err != nil { - log.Printf("Failed to handshake (%s)", err) - continue - } + go ssh.DiscardRequests(reqs) + s.handleChannels(chans) - go ssh.DiscardRequests(reqs) - go s.handleChannels(chans) - } + return nil } func (s *Server) handleChannels(chans <-chan ssh.NewChannel) { @@ -139,46 +115,95 @@ func (s *Server) handleChannel(newChannel ssh.NewChannel) { // Sessions have out-of-band requests such as "shell", "pty-req" and "exec" // We just want to handle "exec". - go func() { - for req := range requests { - switch req.Type { - case "exec": - var reqCmd struct{ Text string } - if err := ssh.Unmarshal(req.Payload, &reqCmd); err != nil { + for req := range requests { + if req.Type != "exec" { + continue + } + var reqCmd struct{ Text string } + if err := ssh.Unmarshal(req.Payload, &reqCmd); err != nil { + log.Printf("failed: %v\n", err) + } + if answer, ok := s.answers[reqCmd.Text]; ok { + if len(answer) == 0 { + channel.Stderr().Write([]byte(fmt.Sprintf("answer empty for %s", reqCmd.Text))) + req.Reply(req.WantReply, nil) + if _, err := channel.SendRequest("exit-status", false, []byte{0, 0, 0, 1}); err != nil { log.Printf("failed: %v\n", err) } - if answer, ok := s.answers[reqCmd.Text]; ok { - if len(answer) == 0 { - channel.Stderr().Write([]byte(fmt.Sprintf("answer empty for %s", reqCmd.Text))) - req.Reply(req.WantReply, nil) - if _, err := channel.SendRequest("exit-status", false, []byte{0, 0, 0, 1}); err != nil { - log.Printf("failed: %v\n", err) - } - } else { - channel.Write(answer) - req.Reply(req.WantReply, nil) - if _, err := channel.SendRequest("exit-status", false, []byte{0, 0, 0, 0}); err != nil { - log.Printf("failed: %v\n", err) - } - } - } else { - channel.Stderr().Write([]byte(fmt.Sprintf("answer not found for %s", reqCmd.Text))) - req.Reply(req.WantReply, nil) - if _, err := channel.SendRequest("exit-status", false, []byte{0, 0, 0, 1}); err != nil { - log.Printf("failed: %v\n", err) - } - } - if err := channel.Close(); err != nil { + } else { + channel.Write(answer) + req.Reply(req.WantReply, nil) + if _, err := channel.SendRequest("exit-status", false, []byte{0, 0, 0, 0}); err != nil { log.Printf("failed: %v\n", err) } - default: - fmt.Println(req.Type) } + } else { + channel.Stderr().Write([]byte(fmt.Sprintf("answer not found for %s", reqCmd.Text))) + req.Reply(req.WantReply, nil) + if _, err := channel.SendRequest("exit-status", false, []byte{0, 0, 0, 1}); err != nil { + log.Printf("failed: %v\n", err) + } + } + if err := channel.Close(); err != nil { + log.Printf("failed: %v\n", err) } - }() + } +} + +func generateHostKey() (ssh.Signer, error) { + key, err := generatePrivateKey(privateKeyBitSize) + if err != nil { + return nil, fmt.Errorf("failed to load private key: %s", err.Error()) + } + + private, err := ssh.ParsePrivateKey(encodePrivateKeyToPEM(key)) + if err != nil { + return nil, fmt.Errorf("failed to parse private key: %s", err.Error()) + } + return private, nil +} + +func generatePrivateKey(bitSize int) (*rsa.PrivateKey, error) { + pk, err := rsa.GenerateKey(rand.Reader, bitSize) + if err != nil { + return nil, err + } + + if err = pk.Validate(); err != nil { + return nil, err + } + + return pk, nil +} + +func encodePrivateKeyToPEM(pk *rsa.PrivateKey) (payload []byte) { + block := pem.Block{ + Type: "RSA PRIVATE KEY", + Headers: nil, + Bytes: x509.MarshalPKCS1PrivateKey(pk), + } + return pem.EncodeToMemory(&block) +} + +func createListener() (net.Listener, error) { + mrand.Seed(time.Now().Unix()) + + var lastErr error + + for i := 0; i < maxAttemptsToCreateListener; i++ { + var l net.Listener + + l, lastErr = net.Listen("tcp", fmt.Sprintf("localhost:%d", randomPort())) + if lastErr != nil { + continue + } + + return l, nil + } + + return nil, lastErr } -// Close shutdown the current ssh server -func (s *Server) Close() error { - return s.ssh.Close() +func randomPort() int { + return mrand.Intn(20000-2000) + 2000 } diff --git a/sshmock/sshmock_test.go b/sshmock/sshmock_test.go index b87014f8..87f3a9c8 100644 --- a/sshmock/sshmock_test.go +++ b/sshmock/sshmock_test.go @@ -6,16 +6,20 @@ import ( "github.com/bmc-toolbox/bmclib/internal/sshclient" ) -func TestServer(t *testing.T) { - expectedAnswer := `world` +func Test_Server(t *testing.T) { + expectedAnswer := "world" command := "hello" answers := map[string][]byte{command: []byte(expectedAnswer)} - s, err := New(answers, true) + s, err := New(answers) if err != nil { - t.Fatalf("found errors during setup Server %s", err.Error()) + t.Fatalf(err.Error()) } - address := s.Address() + shutdown, address, err := s.ListenAndServe() + if err != nil { + t.Fatalf(err.Error()) + } + defer shutdown() sshClient, err := sshclient.New(address, "super", "test") if err != nil { @@ -30,9 +34,4 @@ func TestServer(t *testing.T) { if answer != expectedAnswer { t.Errorf("Expected answer %v: found %v", expectedAnswer, answer) } - - err = s.Close() - if err != nil { - t.Fatalf("unable to close the ssh server %s", err.Error()) - } }