Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Added support for VIF's in Baremetal #3311

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions internal/acceptance/openstack/baremetal/v1/nodes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,3 +272,51 @@ func TestNodesServicingHold(t *testing.T) {
}, nodes.Active)
th.AssertNoErr(t, err)
}

func TestNodesVirtualInterfaces(t *testing.T) {
clients.SkipReleasesBelow(t, "stable/2023.2") // Adjust based on when this feature was added
clients.RequireLong(t)

client, err := clients.NewBareMetalV1Client()
th.AssertNoErr(t, err)
// VIFs were added in API version 1.28, but at least 1.38 is needed for tests to pass
client.Microversion = "1.38"

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

// First, list VIFs (should be empty initially)
vifs, err := nodes.ListVirtualInterfaces(context.TODO(), client, node.UUID).Extract()
th.AssertNoErr(t, err)
th.AssertEquals(t, 0, len(vifs))

// For a real test, we would need a valid VIF ID from the networking service
// Since this is difficult in a test environment, we can test the API call
// with a fake ID and expect it to fail with a specific error
fakeVifID := "1974dcfa-836f-41b2-b541-686c100900e5"

// Try to attach a VIF (this will likely fail with a 404 Not Found since the VIF doesn't exist)
err = nodes.AttachVirtualInterface(context.TODO(), client, node.UUID, nodes.VirtualInterfaceOpts{
ID: fakeVifID,
}).ExtractErr()

// We expect this to fail, but we're testing the API call itself
// In a real environment with valid VIFs, you would check for success instead
if err == nil {
t.Logf("Warning: Expected error when attaching non-existent VIF, but got success. This might indicate the test environment has a VIF with ID %s", fakeVifID)
}

// Try to detach a VIF (this will likely fail with a 404 Not Found)
err = nodes.DetachVirtualInterface(context.TODO(), client, node.UUID, fakeVifID).ExtractErr()

// Again, we expect this to fail in most test environments
if err == nil {
t.Logf("Warning: Expected error when detaching non-existent VIF, but got success. This might indicate the test environment has a VIF with ID %s", fakeVifID)
}

// List VIFs again to confirm state hasn't changed
vifs, err = nodes.ListVirtualInterfaces(context.TODO(), client, node.UUID).Extract()
th.AssertNoErr(t, err)
th.AssertEquals(t, 0, len(vifs))
}
58 changes: 58 additions & 0 deletions openstack/baremetal/v1/nodes/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -998,3 +998,61 @@ func DetachVirtualMedia(ctx context.Context, client *gophercloud.ServiceClient,
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}

// VirtualInterfaceOpts defines options for attaching a VIF to a node
type VirtualInterfaceOpts struct {
// The UUID or name of the VIF
ID string `json:"id" required:"true"`
// The UUID of a port to attach the VIF to. Cannot be specified with PortgroupUUID
PortUUID string `json:"port_uuid,omitempty"`
// The UUID of a portgroup to attach the VIF to. Cannot be specified with PortUUID
PortgroupUUID string `json:"portgroup_uuid,omitempty"`
}

// VirtualInterfaceOptsBuilder allows extensions to add additional parameters to the
// AttachVirtualInterface request.
type VirtualInterfaceOptsBuilder interface {
ToVirtualInterfaceMap() (map[string]any, error)
}

// ToVirtualInterfaceMap assembles a request body based on the contents of a VirtualInterfaceOpts.
func (opts VirtualInterfaceOpts) ToVirtualInterfaceMap() (map[string]any, error) {
if opts.PortUUID != "" && opts.PortgroupUUID != "" {
return nil, fmt.Errorf("cannot specify both port_uuid and portgroup_uuid")
}

return gophercloud.BuildRequestBody(opts, "")
}

// ListVirtualInterfaces returns a list of VIFs that are attached to the node.
func ListVirtualInterfaces(ctx context.Context, client *gophercloud.ServiceClient, id string) (r ListVirtualInterfacesResult) {
resp, err := client.Get(ctx, virtualInterfaceURL(client, id), &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}

// AttachVirtualInterface attaches a VIF to a node.
func AttachVirtualInterface(ctx context.Context, client *gophercloud.ServiceClient, id string, opts VirtualInterfaceOptsBuilder) (r VirtualInterfaceAttachResult) {
reqBody, err := opts.ToVirtualInterfaceMap()
if err != nil {
r.Err = err
return
}

resp, err := client.Post(ctx, virtualInterfaceURL(client, id), reqBody, nil, &gophercloud.RequestOpts{
OkCodes: []int{204},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}

// DetachVirtualInterface detaches a VIF from a node.
func DetachVirtualInterface(ctx context.Context, client *gophercloud.ServiceClient, id string, vifID string) (r VirtualInterfaceDetachResult) {
resp, err := client.Delete(ctx, virtualInterfaceDeleteURL(client, id, vifID), &gophercloud.RequestOpts{
OkCodes: []int{204},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
37 changes: 37 additions & 0 deletions openstack/baremetal/v1/nodes/results.go
Original file line number Diff line number Diff line change
Expand Up @@ -660,3 +660,40 @@ type VirtualMediaAttachResult struct {
type VirtualMediaDetachResult struct {
gophercloud.ErrResult
}

// VirtualInterfaceAttachResult is the response from an AttachVirtualInterface operation.
type VirtualInterfaceAttachResult struct {
gophercloud.ErrResult
}

// VirtualInterfaceDetachResult is the response from a DetachVirtualInterface operation.
type VirtualInterfaceDetachResult struct {
gophercloud.ErrResult
}

// VIF represents a virtual interface attached to a node.
type VIF struct {
// The UUID or name of the VIF
ID string `json:"id"`
}

// ListVirtualInterfacesResult is the response from a ListVirtualInterfaces operation.
type ListVirtualInterfacesResult struct {
gophercloud.Result
gophercloud.HeaderResult
}

// Extract interprets any ListVirtualInterfacesResult as a list of VIFs.
func (r ListVirtualInterfacesResult) Extract() ([]VIF, error) {
var s struct {
VIFs []VIF `json:"vifs"`
}

err := r.Result.ExtractInto(&s)
return s.VIFs, err
}

// ExtractHeader interprets any ListVirtualInterfacesResult as a HeaderResult.
func (r ListVirtualInterfacesResult) ExtractHeader() (gophercloud.HeaderResult, error) {
return r.HeaderResult, nil
}
75 changes: 75 additions & 0 deletions openstack/baremetal/v1/nodes/testing/fixtures_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1751,3 +1751,78 @@ func HandleDetachVirtualMediaSuccessfully(t *testing.T, withType bool) {
w.WriteHeader(http.StatusNoContent)
})
}

// HandleListVirtualInterfacesSuccessfully sets up the test server to respond to a ListVirtualInterfaces request
func HandleListVirtualInterfacesSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/nodes/1234asdf/vifs",
func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestHeader(t, r, "Accept", "application/json")

w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
{
"vifs": [
{
"id": "1974dcfa-836f-41b2-b541-686c100900e5"
}
]
}`)
})
}

// HandleAttachVirtualInterfaceSuccessfully sets up the test server to respond to an AttachVirtualInterface request
func HandleAttachVirtualInterfaceSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/nodes/1234asdf/vifs",
func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestHeader(t, r, "Content-Type", "application/json")
th.TestHeader(t, r, "Accept", "application/json")
th.TestJSONRequest(t, r, `{"id":"1974dcfa-836f-41b2-b541-686c100900e5"}`)

w.WriteHeader(http.StatusNoContent)
})
}

// HandleAttachVirtualInterfaceWithPortSuccessfully sets up the test server to respond to an AttachVirtualInterface request with port
func HandleAttachVirtualInterfaceWithPortSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/nodes/1234asdf/vifs",
func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestHeader(t, r, "Content-Type", "application/json")
th.TestHeader(t, r, "Accept", "application/json")
th.TestJSONRequest(t, r, `{"id":"1974dcfa-836f-41b2-b541-686c100900e5","port_uuid":"b2f96298-5172-45e9-b174-8d1ba936ab47"}`)

w.WriteHeader(http.StatusNoContent)
})
}

// HandleAttachVirtualInterfaceWithPortgroupSuccessfully sets up the test server to respond to an AttachVirtualInterface request with portgroup
func HandleAttachVirtualInterfaceWithPortgroupSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/nodes/1234asdf/vifs",
func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestHeader(t, r, "Content-Type", "application/json")
th.TestHeader(t, r, "Accept", "application/json")
th.TestJSONRequest(t, r, `{"id":"1974dcfa-836f-41b2-b541-686c100900e5","portgroup_uuid":"c24944b5-a52e-4c5c-9c0a-52a0235a08a2"}`)

w.WriteHeader(http.StatusNoContent)
})
}

// HandleDetachVirtualInterfaceSuccessfully sets up the test server to respond to a DetachVirtualInterface request
func HandleDetachVirtualInterfaceSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/nodes/1234asdf/vifs/1974dcfa-836f-41b2-b541-686c100900e5",
func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestHeader(t, r, "Accept", "application/json")

w.WriteHeader(http.StatusNoContent)
})
}
80 changes: 80 additions & 0 deletions openstack/baremetal/v1/nodes/testing/requests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -829,3 +829,83 @@ func TestVirtualMediaDetachWithTypes(t *testing.T) {
err := nodes.DetachVirtualMedia(context.TODO(), c, "1234asdf", opts).ExtractErr()
th.AssertNoErr(t, err)
}

func TestListVirtualInterfaces(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleListVirtualInterfacesSuccessfully(t)

c := client.ServiceClient()
actual, err := nodes.ListVirtualInterfaces(context.TODO(), c, "1234asdf").Extract()
th.AssertNoErr(t, err)

expected := []nodes.VIF{
{
ID: "1974dcfa-836f-41b2-b541-686c100900e5",
},
}

th.CheckDeepEquals(t, expected, actual)
}

func TestAttachVirtualInterface(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleAttachVirtualInterfaceSuccessfully(t)

c := client.ServiceClient()
opts := nodes.VirtualInterfaceOpts{
ID: "1974dcfa-836f-41b2-b541-686c100900e5",
}
err := nodes.AttachVirtualInterface(context.TODO(), c, "1234asdf", opts).ExtractErr()
th.AssertNoErr(t, err)
}

func TestAttachVirtualInterfaceWithPort(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleAttachVirtualInterfaceWithPortSuccessfully(t)

c := client.ServiceClient()
opts := nodes.VirtualInterfaceOpts{
ID: "1974dcfa-836f-41b2-b541-686c100900e5",
PortUUID: "b2f96298-5172-45e9-b174-8d1ba936ab47",
}
err := nodes.AttachVirtualInterface(context.TODO(), c, "1234asdf", opts).ExtractErr()
th.AssertNoErr(t, err)
}

func TestAttachVirtualInterfaceWithPortgroup(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleAttachVirtualInterfaceWithPortgroupSuccessfully(t)

c := client.ServiceClient()
opts := nodes.VirtualInterfaceOpts{
ID: "1974dcfa-836f-41b2-b541-686c100900e5",
PortgroupUUID: "c24944b5-a52e-4c5c-9c0a-52a0235a08a2",
}
err := nodes.AttachVirtualInterface(context.TODO(), c, "1234asdf", opts).ExtractErr()
th.AssertNoErr(t, err)
}

func TestDetachVirtualInterface(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleDetachVirtualInterfaceSuccessfully(t)

c := client.ServiceClient()
err := nodes.DetachVirtualInterface(context.TODO(), c, "1234asdf", "1974dcfa-836f-41b2-b541-686c100900e5").ExtractErr()
th.AssertNoErr(t, err)
}

func TestVirtualInterfaceOptsValidation(t *testing.T) {
opts := nodes.VirtualInterfaceOpts{
ID: "1974dcfa-836f-41b2-b541-686c100900e5",
PortUUID: "b2f96298-5172-45e9-b174-8d1ba936ab47",
PortgroupUUID: "c24944b5-a52e-4c5c-9c0a-52a0235a08a2",
}

_, err := opts.ToVirtualInterfaceMap()
th.AssertEquals(t, err.Error(), "cannot specify both port_uuid and portgroup_uuid")
}
8 changes: 8 additions & 0 deletions openstack/baremetal/v1/nodes/urls.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,11 @@ func firmwareListURL(client *gophercloud.ServiceClient, id string) string {
func virtualMediaURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("nodes", id, "vmedia")
}

func virtualInterfaceURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("nodes", id, "vifs")
}

func virtualInterfaceDeleteURL(client *gophercloud.ServiceClient, id string, vifID string) string {
return client.ServiceURL("nodes", id, "vifs", vifID)
}
Loading