Skip to content

Commit

Permalink
Merge pull request gophercloud#3014 from dtantsur/baremetal-servicing
Browse files Browse the repository at this point in the history
baremetal: add support for servicing
  • Loading branch information
EmilienM authored Apr 22, 2024
2 parents df37077 + b728d96 commit 8b1eebe
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/functional-baremetal.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ jobs:
IRONIC_VM_LOG_DIR=/opt/stack/new/ironic-bm-logs
IRONIC_VM_SPECS_RAM=1024
IRONIC_DEFAULT_DEPLOY_INTERFACE=direct
IRONIC_ENABLED_DEPLOY_INTERFACES=direct
IRONIC_ENABLED_DEPLOY_INTERFACES=direct,fake
SWIFT_ENABLE_TEMPURLS=True
SWIFT_TEMPURL_KEY=secretkey
enabled_services: 'ir-api,ir-cond,s-account,s-container,s-object,s-proxy,q-svc,q-agt,q-dhcp,q-l3,q-meta,-cinder,-c-sch,-c-api,-c-vol,-c-bak,-ovn,-ovn-controller,-ovn-northd,-q-ovn-metadata-agent'
Expand Down
82 changes: 77 additions & 5 deletions internal/acceptance/openstack/baremetal/v1/baremetal.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package v1
import (
"context"
"testing"
"time"

"github.com/gophercloud/gophercloud/v2"
"github.com/gophercloud/gophercloud/v2/internal/acceptance/tools"
Expand Down Expand Up @@ -36,7 +37,15 @@ func CreateNode(t *testing.T, client *gophercloud.ServiceClient) (*nodes.Node, e

// DeleteNode deletes a bare metal node via its UUID.
func DeleteNode(t *testing.T, client *gophercloud.ServiceClient, node *nodes.Node) {
err := nodes.Delete(context.TODO(), client, node.UUID).ExtractErr()
// Force deletion of provisioned nodes requires maintenance mode.
err := nodes.SetMaintenance(context.TODO(), client, node.UUID, nodes.MaintenanceOpts{
Reason: "forced deletion",
}).ExtractErr()
if err != nil {
t.Fatalf("Unable to move node %s into maintenance mode: %s", node.UUID, err)
}

err = nodes.Delete(context.TODO(), client, node.UUID).ExtractErr()
if err != nil {
t.Fatalf("Unable to delete node %s: %s", node.UUID, err)
}
Expand Down Expand Up @@ -67,15 +76,16 @@ func DeleteAllocation(t *testing.T, client *gophercloud.ServiceClient, allocatio
t.Logf("Deleted allocation: %s", allocation.UUID)
}

// CreateFakeNode creates a node with fake-hardware to use for port tests.
// CreateFakeNode creates a node with fake-hardware.
func CreateFakeNode(t *testing.T, client *gophercloud.ServiceClient) (*nodes.Node, error) {
name := tools.RandomString("ACPTTEST", 16)
t.Logf("Attempting to create bare metal node: %s", name)

node, err := nodes.Create(context.TODO(), client, nodes.CreateOpts{
Name: name,
Driver: "fake-hardware",
BootInterface: "fake",
Name: name,
Driver: "fake-hardware",
BootInterface: "fake",
DeployInterface: "fake",
DriverInfo: map[string]interface{}{
"ipmi_port": "6230",
"ipmi_username": "admin",
Expand All @@ -89,6 +99,68 @@ func CreateFakeNode(t *testing.T, client *gophercloud.ServiceClient) (*nodes.Nod
return node, err
}

func ChangeProvisionStateAndWait(ctx context.Context, client *gophercloud.ServiceClient, node *nodes.Node,
change nodes.ProvisionStateOpts, expectedState nodes.ProvisionState) (*nodes.Node, error) {
err := nodes.ChangeProvisionState(ctx, client, node.UUID, change).ExtractErr()
if err != nil {
return node, err
}

err = nodes.WaitForProvisionState(ctx, client, node.UUID, expectedState)
if err != nil {
return node, err
}

return nodes.Get(ctx, client, node.UUID).Extract()
}

// DeployFakeNode deploys a node that uses fake-hardware.
func DeployFakeNode(t *testing.T, client *gophercloud.ServiceClient, node *nodes.Node) (*nodes.Node, error) {
ctx, cancel := context.WithTimeout(context.TODO(), 30*time.Second)
defer cancel()

currentState := node.ProvisionState

if currentState == string(nodes.Enroll) {
t.Logf("moving fake node %s to manageable", node.UUID)
err := nodes.ChangeProvisionState(ctx, client, node.UUID, nodes.ProvisionStateOpts{
Target: nodes.TargetManage,
}).ExtractErr()
if err != nil {
return node, err
}

err = nodes.WaitForProvisionState(ctx, client, node.UUID, nodes.Manageable)
if err != nil {
return node, err
}

currentState = string(nodes.Manageable)
}

if currentState == string(nodes.Manageable) {
t.Logf("moving fake node %s to available", node.UUID)
err := nodes.ChangeProvisionState(ctx, client, node.UUID, nodes.ProvisionStateOpts{
Target: nodes.TargetProvide,
}).ExtractErr()
if err != nil {
return node, err
}

err = nodes.WaitForProvisionState(ctx, client, node.UUID, nodes.Available)
if err != nil {
return node, err
}

currentState = string(nodes.Available)
}

t.Logf("deploying fake node %s", node.UUID)
return ChangeProvisionStateAndWait(ctx, client, node, nodes.ProvisionStateOpts{
Target: nodes.TargetActive,
}, nodes.Active)
}

// CreatePort - creates a port for a node with a fixed Address
func CreatePort(t *testing.T, client *gophercloud.ServiceClient, node *nodes.Node) (*ports.Port, error) {
mac := "e6:72:1f:52:00:f4"
Expand Down
30 changes: 30 additions & 0 deletions internal/acceptance/openstack/baremetal/v1/nodes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,3 +242,33 @@ func TestNodesVirtualMedia(t *testing.T) {
err = nodes.DetachVirtualMedia(context.TODO(), client, node.UUID, nodes.DetachVirtualMediaOpts{}).ExtractErr()
th.AssertNoErr(t, err)
}

func TestNodesServicingHold(t *testing.T) {
clients.SkipReleasesBelow(t, "stable/2023.2")
clients.RequireLong(t)

client, err := clients.NewBareMetalV1Client()
th.AssertNoErr(t, err)
client.Microversion = "1.87"

node, err := CreateFakeNode(t, client)
th.AssertNoErr(t, err)
defer DeleteNode(t, client, node)

node, err = DeployFakeNode(t, client, node)
th.AssertNoErr(t, err)

ctx, cancel := context.WithTimeout(context.TODO(), 30*time.Second)
defer cancel()

_, err = ChangeProvisionStateAndWait(ctx, client, node, nodes.ProvisionStateOpts{
Target: nodes.TargetService,
ServiceSteps: []nodes.ServiceStep{
{
Interface: nodes.InterfaceDeploy,
Step: nodes.StepReboot,
},
},
}, nodes.Active)
th.AssertNoErr(t, err)
}
20 changes: 20 additions & 0 deletions openstack/baremetal/v1/nodes/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ const (
Deploying ProvisionState = "deploying"
DeployFail ProvisionState = "deploy failed"
DeployDone ProvisionState = "deploy complete"
DeployHold ProvisionState = "deploy hold"
Deleting ProvisionState = "deleting"
Deleted ProvisionState = "deleted"
Cleaning ProvisionState = "cleaning"
CleanWait ProvisionState = "clean wait"
CleanFail ProvisionState = "clean failed"
CleanHold ProvisionState = "clean hold"
Error ProvisionState = "error"
Rebuild ProvisionState = "rebuild"
Inspecting ProvisionState = "inspecting"
Expand All @@ -46,6 +48,10 @@ const (
UnrescueFail ProvisionState = "unrescue failed"
RescueWait ProvisionState = "rescue wait"
Unrescuing ProvisionState = "unrescuing"
Servicing ProvisionState = "servicing"
ServiceWait ProvisionState = "service wait"
ServiceFail ProvisionState = "service fail"
ServiceHold ProvisionState = "service hold"
)

// TargetProvisionState is used when setting the provision state for a node.
Expand All @@ -63,6 +69,16 @@ const (
TargetRescue TargetProvisionState = "rescue"
TargetUnrescue TargetProvisionState = "unrescue"
TargetRebuild TargetProvisionState = "rebuild"
TargetService TargetProvisionState = "service"
TargetUnhold TargetProvisionState = "unhold"
)

const (
StepHold string = "hold"
StepWait string = "wait"
StepPowerOn string = "power_on"
StepPowerOff string = "power_off"
StepReboot string = "reboot"
)

// ListOpts allows the filtering and sorting of paginated collections through
Expand Down Expand Up @@ -430,6 +446,9 @@ type CleanStep struct {
Args map[string]interface{} `json:"args,omitempty"`
}

// A service step looks the same as a cleaning step.
type ServiceStep = CleanStep

// A deploy step has required keys ‘interface’, ‘step’, ’args’ and ’priority’.
// The value for ‘args’ is a keyword variable argument dictionary that is passed to the deploy step
// method. Priority is a numeric priority at which the step is running.
Expand Down Expand Up @@ -461,6 +480,7 @@ type ProvisionStateOpts struct {
ConfigDrive interface{} `json:"configdrive,omitempty"`
CleanSteps []CleanStep `json:"clean_steps,omitempty"`
DeploySteps []DeployStep `json:"deploy_steps,omitempty"`
ServiceSteps []ServiceStep `json:"service_steps,omitempty"`
RescuePassword string `json:"rescue_password,omitempty"`
}

Expand Down
3 changes: 3 additions & 0 deletions openstack/baremetal/v1/nodes/results.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ type Node struct {
// Current deploy step.
DeployStep map[string]interface{} `json:"deploy_step"`

// Current service step.
ServiceStep map[string]interface{} `json:"service_step"`

// String which can be used by external schedulers to identify this Node as a unit of a specific type of resource.
// For more details, see: https://docs.openstack.org/ironic/latest/install/configure-nova-flavors.html
ResourceClass string `json:"resource_class"`
Expand Down
24 changes: 24 additions & 0 deletions openstack/baremetal/v1/nodes/testing/fixtures_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,21 @@ const NodeProvisionStateConfigDriveBody = `
}
`

const NodeProvisionStateServiceBody = `
{
"target": "service",
"service_steps": [
{
"interface": "bios",
"step": "apply_configuration",
"args": {
"settings": []
}
}
]
}
`

const NodeBIOSSettingsBody = `
{
"bios": [
Expand Down Expand Up @@ -1458,6 +1473,15 @@ func HandleNodeChangeProvisionStateConfigDrive(t *testing.T) {
})
}

func HandleNodeChangeProvisionStateService(t *testing.T) {
th.Mux.HandleFunc("/nodes/1234asdf/states/provision", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestJSONRequest(t, r, NodeProvisionStateServiceBody)
w.WriteHeader(http.StatusAccepted)
})
}

// HandleChangePowerStateSuccessfully sets up the test server to respond to a change power state request for a node
func HandleChangePowerStateSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/nodes/1234asdf/states/power", func(w http.ResponseWriter, r *http.Request) {
Expand Down
22 changes: 22 additions & 0 deletions openstack/baremetal/v1/nodes/testing/requests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,28 @@ func TestCleanStepRequiresStep(t *testing.T) {
}
}

func TestNodeChangeProvisionStateService(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleNodeChangeProvisionStateService(t)

c := client.ServiceClient()
err := nodes.ChangeProvisionState(context.TODO(), c, "1234asdf", nodes.ProvisionStateOpts{
Target: nodes.TargetService,
ServiceSteps: []nodes.ServiceStep{
{
Interface: nodes.InterfaceBIOS,
Step: "apply_configuration",
Args: map[string]interface{}{
"settings": []string{},
},
},
},
}).ExtractErr()

th.AssertNoErr(t, err)
}

func TestChangePowerState(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
Expand Down

0 comments on commit 8b1eebe

Please sign in to comment.