github.com/vnpaycloud-console/gophercloud/v2@v2.0.5/openstack/networking/v2/ports/testing/requests_test.go (about)

     1  package testing
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"net/url"
     8  	"testing"
     9  	"time"
    10  
    11  	fake "github.com/vnpaycloud-console/gophercloud/v2/openstack/networking/v2/common"
    12  	"github.com/vnpaycloud-console/gophercloud/v2/openstack/networking/v2/extensions/extradhcpopts"
    13  	"github.com/vnpaycloud-console/gophercloud/v2/openstack/networking/v2/extensions/portsecurity"
    14  	"github.com/vnpaycloud-console/gophercloud/v2/openstack/networking/v2/ports"
    15  	"github.com/vnpaycloud-console/gophercloud/v2/pagination"
    16  	th "github.com/vnpaycloud-console/gophercloud/v2/testhelper"
    17  )
    18  
    19  func TestList(t *testing.T) {
    20  	th.SetupHTTP()
    21  	defer th.TeardownHTTP()
    22  
    23  	th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) {
    24  		th.TestMethod(t, r, "GET")
    25  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
    26  
    27  		w.Header().Add("Content-Type", "application/json")
    28  		w.WriteHeader(http.StatusOK)
    29  
    30  		fmt.Fprint(w, ListResponse)
    31  	})
    32  
    33  	count := 0
    34  
    35  	err := ports.List(fake.ServiceClient(), ports.ListOpts{}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) {
    36  		count++
    37  		actual, err := ports.ExtractPorts(page)
    38  		if err != nil {
    39  			t.Errorf("Failed to extract subnets: %v", err)
    40  			return false, nil
    41  		}
    42  
    43  		expected := []ports.Port{
    44  			{
    45  				Status:       "ACTIVE",
    46  				Name:         "",
    47  				AdminStateUp: true,
    48  				NetworkID:    "70c1db1f-b701-45bd-96e0-a313ee3430b3",
    49  				TenantID:     "",
    50  				DeviceOwner:  "network:router_gateway",
    51  				MACAddress:   "fa:16:3e:58:42:ed",
    52  				FixedIPs: []ports.IP{
    53  					{
    54  						SubnetID:  "008ba151-0b8c-4a67-98b5-0d2b87666062",
    55  						IPAddress: "172.24.4.2",
    56  					},
    57  				},
    58  				ID:             "d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b",
    59  				SecurityGroups: []string{},
    60  				DeviceID:       "9ae135f4-b6e0-4dad-9e91-3c223e385824",
    61  				CreatedAt:      time.Date(2019, time.June, 30, 4, 15, 37, 0, time.UTC),
    62  				UpdatedAt:      time.Date(2019, time.June, 30, 5, 18, 49, 0, time.UTC),
    63  			},
    64  		}
    65  
    66  		th.CheckDeepEquals(t, expected, actual)
    67  
    68  		return true, nil
    69  	})
    70  
    71  	th.AssertNoErr(t, err)
    72  
    73  	if count != 1 {
    74  		t.Errorf("Expected 1 page, got %d", count)
    75  	}
    76  }
    77  
    78  func TestListWithExtensions(t *testing.T) {
    79  	th.SetupHTTP()
    80  	defer th.TeardownHTTP()
    81  
    82  	th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) {
    83  		th.TestMethod(t, r, "GET")
    84  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
    85  
    86  		w.Header().Add("Content-Type", "application/json")
    87  		w.WriteHeader(http.StatusOK)
    88  
    89  		fmt.Fprint(w, ListResponse)
    90  	})
    91  
    92  	type portWithExt struct {
    93  		ports.Port
    94  		portsecurity.PortSecurityExt
    95  	}
    96  
    97  	var allPorts []portWithExt
    98  
    99  	allPages, err := ports.List(fake.ServiceClient(), ports.ListOpts{}).AllPages(context.TODO())
   100  	th.AssertNoErr(t, err)
   101  
   102  	err = ports.ExtractPortsInto(allPages, &allPorts)
   103  	th.AssertNoErr(t, err)
   104  
   105  	th.AssertEquals(t, allPorts[0].Status, "ACTIVE")
   106  	th.AssertEquals(t, allPorts[0].PortSecurityEnabled, false)
   107  }
   108  
   109  func TestGet(t *testing.T) {
   110  	th.SetupHTTP()
   111  	defer th.TeardownHTTP()
   112  
   113  	th.Mux.HandleFunc("/v2.0/ports/46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", func(w http.ResponseWriter, r *http.Request) {
   114  		th.TestMethod(t, r, "GET")
   115  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   116  
   117  		w.Header().Add("Content-Type", "application/json")
   118  		w.WriteHeader(http.StatusOK)
   119  
   120  		fmt.Fprint(w, GetResponse)
   121  	})
   122  
   123  	n, err := ports.Get(context.TODO(), fake.ServiceClient(), "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2").Extract()
   124  	th.AssertNoErr(t, err)
   125  
   126  	th.AssertEquals(t, n.Status, "ACTIVE")
   127  	th.AssertEquals(t, n.Name, "")
   128  	th.AssertEquals(t, n.AdminStateUp, true)
   129  	th.AssertEquals(t, n.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7")
   130  	th.AssertEquals(t, n.TenantID, "7e02058126cc4950b75f9970368ba177")
   131  	th.AssertEquals(t, n.DeviceOwner, "network:router_interface")
   132  	th.AssertEquals(t, n.MACAddress, "fa:16:3e:23:fd:d7")
   133  	th.AssertDeepEquals(t, n.FixedIPs, []ports.IP{
   134  		{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.1"},
   135  	})
   136  	th.AssertEquals(t, n.ID, "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2")
   137  	th.AssertDeepEquals(t, n.SecurityGroups, []string{})
   138  	th.AssertEquals(t, n.Status, "ACTIVE")
   139  	th.AssertEquals(t, n.DeviceID, "5e3898d7-11be-483e-9732-b2f5eccd2b2e")
   140  	th.AssertEquals(t, n.CreatedAt, time.Date(2019, time.June, 30, 4, 15, 37, 0, time.UTC))
   141  	th.AssertEquals(t, n.UpdatedAt, time.Date(2019, time.June, 30, 5, 18, 49, 0, time.UTC))
   142  }
   143  
   144  func TestGetWithExtensions(t *testing.T) {
   145  	th.SetupHTTP()
   146  	defer th.TeardownHTTP()
   147  
   148  	th.Mux.HandleFunc("/v2.0/ports/46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", func(w http.ResponseWriter, r *http.Request) {
   149  		th.TestMethod(t, r, "GET")
   150  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   151  
   152  		w.Header().Add("Content-Type", "application/json")
   153  		w.WriteHeader(http.StatusOK)
   154  
   155  		fmt.Fprint(w, GetResponse)
   156  	})
   157  
   158  	var portWithExtensions struct {
   159  		ports.Port
   160  		portsecurity.PortSecurityExt
   161  	}
   162  
   163  	err := ports.Get(context.TODO(), fake.ServiceClient(), "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2").ExtractInto(&portWithExtensions)
   164  	th.AssertNoErr(t, err)
   165  
   166  	th.AssertEquals(t, portWithExtensions.Status, "ACTIVE")
   167  	th.AssertEquals(t, portWithExtensions.PortSecurityEnabled, false)
   168  }
   169  
   170  func TestCreate(t *testing.T) {
   171  	th.SetupHTTP()
   172  	defer th.TeardownHTTP()
   173  
   174  	th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) {
   175  		th.TestMethod(t, r, "POST")
   176  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   177  		th.TestHeader(t, r, "Content-Type", "application/json")
   178  		th.TestHeader(t, r, "Accept", "application/json")
   179  		th.TestJSONRequest(t, r, CreateRequest)
   180  
   181  		w.Header().Add("Content-Type", "application/json")
   182  		w.WriteHeader(http.StatusCreated)
   183  
   184  		fmt.Fprint(w, CreateResponse)
   185  	})
   186  
   187  	asu := true
   188  	options := ports.CreateOpts{
   189  		Name:         "private-port",
   190  		AdminStateUp: &asu,
   191  		NetworkID:    "a87cc70a-3e15-4acf-8205-9b711a3531b7",
   192  		FixedIPs: []ports.IP{
   193  			{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"},
   194  		},
   195  		SecurityGroups: &[]string{"foo"},
   196  		AllowedAddressPairs: []ports.AddressPair{
   197  			{IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"},
   198  		},
   199  	}
   200  	n, err := ports.Create(context.TODO(), fake.ServiceClient(), options).Extract()
   201  	th.AssertNoErr(t, err)
   202  
   203  	th.AssertEquals(t, n.Status, "DOWN")
   204  	th.AssertEquals(t, n.Name, "private-port")
   205  	th.AssertEquals(t, n.AdminStateUp, true)
   206  	th.AssertEquals(t, n.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7")
   207  	th.AssertEquals(t, n.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa")
   208  	th.AssertEquals(t, n.DeviceOwner, "")
   209  	th.AssertEquals(t, n.MACAddress, "fa:16:3e:c9:cb:f0")
   210  	th.AssertDeepEquals(t, n.FixedIPs, []ports.IP{
   211  		{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"},
   212  	})
   213  	th.AssertEquals(t, n.ID, "65c0ee9f-d634-4522-8954-51021b570b0d")
   214  	th.AssertDeepEquals(t, n.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"})
   215  	th.AssertDeepEquals(t, n.AllowedAddressPairs, []ports.AddressPair{
   216  		{IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"},
   217  	})
   218  }
   219  
   220  func TestCreateOmitSecurityGroups(t *testing.T) {
   221  	th.SetupHTTP()
   222  	defer th.TeardownHTTP()
   223  
   224  	th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) {
   225  		th.TestMethod(t, r, "POST")
   226  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   227  		th.TestHeader(t, r, "Content-Type", "application/json")
   228  		th.TestHeader(t, r, "Accept", "application/json")
   229  		th.TestJSONRequest(t, r, CreateOmitSecurityGroupsRequest)
   230  
   231  		w.Header().Add("Content-Type", "application/json")
   232  		w.WriteHeader(http.StatusCreated)
   233  
   234  		fmt.Fprint(w, CreateOmitSecurityGroupsResponse)
   235  	})
   236  
   237  	asu := true
   238  	options := ports.CreateOpts{
   239  		Name:         "private-port",
   240  		AdminStateUp: &asu,
   241  		NetworkID:    "a87cc70a-3e15-4acf-8205-9b711a3531b7",
   242  		FixedIPs: []ports.IP{
   243  			{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"},
   244  		},
   245  		AllowedAddressPairs: []ports.AddressPair{
   246  			{IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"},
   247  		},
   248  	}
   249  	n, err := ports.Create(context.TODO(), fake.ServiceClient(), options).Extract()
   250  	th.AssertNoErr(t, err)
   251  
   252  	th.AssertEquals(t, n.Status, "DOWN")
   253  	th.AssertEquals(t, n.Name, "private-port")
   254  	th.AssertEquals(t, n.AdminStateUp, true)
   255  	th.AssertEquals(t, n.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7")
   256  	th.AssertEquals(t, n.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa")
   257  	th.AssertEquals(t, n.DeviceOwner, "")
   258  	th.AssertEquals(t, n.MACAddress, "fa:16:3e:c9:cb:f0")
   259  	th.AssertDeepEquals(t, n.FixedIPs, []ports.IP{
   260  		{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"},
   261  	})
   262  	th.AssertEquals(t, n.ID, "65c0ee9f-d634-4522-8954-51021b570b0d")
   263  	th.AssertDeepEquals(t, n.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"})
   264  	th.AssertDeepEquals(t, n.AllowedAddressPairs, []ports.AddressPair{
   265  		{IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"},
   266  	})
   267  }
   268  
   269  func TestCreateWithNoSecurityGroup(t *testing.T) {
   270  	th.SetupHTTP()
   271  	defer th.TeardownHTTP()
   272  
   273  	th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) {
   274  		th.TestMethod(t, r, "POST")
   275  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   276  		th.TestHeader(t, r, "Content-Type", "application/json")
   277  		th.TestHeader(t, r, "Accept", "application/json")
   278  		th.TestJSONRequest(t, r, CreateWithNoSecurityGroupsRequest)
   279  
   280  		w.Header().Add("Content-Type", "application/json")
   281  		w.WriteHeader(http.StatusCreated)
   282  
   283  		fmt.Fprint(w, CreateWithNoSecurityGroupsResponse)
   284  	})
   285  
   286  	asu := true
   287  	options := ports.CreateOpts{
   288  		Name:         "private-port",
   289  		AdminStateUp: &asu,
   290  		NetworkID:    "a87cc70a-3e15-4acf-8205-9b711a3531b7",
   291  		FixedIPs: []ports.IP{
   292  			{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"},
   293  		},
   294  		SecurityGroups: &[]string{},
   295  		AllowedAddressPairs: []ports.AddressPair{
   296  			{IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"},
   297  		},
   298  	}
   299  	n, err := ports.Create(context.TODO(), fake.ServiceClient(), options).Extract()
   300  	th.AssertNoErr(t, err)
   301  
   302  	th.AssertEquals(t, n.Status, "DOWN")
   303  	th.AssertEquals(t, n.Name, "private-port")
   304  	th.AssertEquals(t, n.AdminStateUp, true)
   305  	th.AssertEquals(t, n.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7")
   306  	th.AssertEquals(t, n.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa")
   307  	th.AssertEquals(t, n.DeviceOwner, "")
   308  	th.AssertEquals(t, n.MACAddress, "fa:16:3e:c9:cb:f0")
   309  	th.AssertDeepEquals(t, n.FixedIPs, []ports.IP{
   310  		{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"},
   311  	})
   312  	th.AssertEquals(t, n.ID, "65c0ee9f-d634-4522-8954-51021b570b0d")
   313  	th.AssertDeepEquals(t, n.AllowedAddressPairs, []ports.AddressPair{
   314  		{IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"},
   315  	})
   316  }
   317  
   318  func TestCreateWithPropagateUplinkStatus(t *testing.T) {
   319  	th.SetupHTTP()
   320  	defer th.TeardownHTTP()
   321  
   322  	th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) {
   323  		th.TestMethod(t, r, "POST")
   324  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   325  		th.TestHeader(t, r, "Content-Type", "application/json")
   326  		th.TestHeader(t, r, "Accept", "application/json")
   327  		th.TestJSONRequest(t, r, CreatePropagateUplinkStatusRequest)
   328  
   329  		w.Header().Add("Content-Type", "application/json")
   330  		w.WriteHeader(http.StatusCreated)
   331  
   332  		fmt.Fprint(w, CreatePropagateUplinkStatusResponse)
   333  	})
   334  
   335  	asu := true
   336  	propagateUplinkStatus := true
   337  	options := ports.CreateOpts{
   338  		Name:         "private-port",
   339  		AdminStateUp: &asu,
   340  		NetworkID:    "a87cc70a-3e15-4acf-8205-9b711a3531b7",
   341  		FixedIPs: []ports.IP{
   342  			{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"},
   343  		},
   344  		PropagateUplinkStatus: &propagateUplinkStatus,
   345  	}
   346  	n, err := ports.Create(context.TODO(), fake.ServiceClient(), options).Extract()
   347  	th.AssertNoErr(t, err)
   348  
   349  	th.AssertEquals(t, n.Status, "DOWN")
   350  	th.AssertEquals(t, n.Name, "private-port")
   351  	th.AssertEquals(t, n.AdminStateUp, true)
   352  	th.AssertEquals(t, n.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7")
   353  	th.AssertEquals(t, n.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa")
   354  	th.AssertEquals(t, n.DeviceOwner, "")
   355  	th.AssertEquals(t, n.MACAddress, "fa:16:3e:c9:cb:f0")
   356  	th.AssertDeepEquals(t, n.FixedIPs, []ports.IP{
   357  		{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"},
   358  	})
   359  	th.AssertEquals(t, n.ID, "65c0ee9f-d634-4522-8954-51021b570b0d")
   360  	th.AssertEquals(t, n.PropagateUplinkStatus, propagateUplinkStatus)
   361  }
   362  
   363  func TestCreateWithValueSpecs(t *testing.T) {
   364  	th.SetupHTTP()
   365  	defer th.TeardownHTTP()
   366  
   367  	th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) {
   368  		th.TestMethod(t, r, "POST")
   369  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   370  		th.TestHeader(t, r, "Content-Type", "application/json")
   371  		th.TestHeader(t, r, "Accept", "application/json")
   372  		th.TestJSONRequest(t, r, CreateValueSpecRequest)
   373  
   374  		w.Header().Add("Content-Type", "application/json")
   375  		w.WriteHeader(http.StatusCreated)
   376  
   377  		fmt.Fprint(w, CreateValueSpecResponse)
   378  	})
   379  
   380  	asu := true
   381  	options := ports.CreateOpts{
   382  		Name:         "private-port",
   383  		AdminStateUp: &asu,
   384  		NetworkID:    "a87cc70a-3e15-4acf-8205-9b711a3531b7",
   385  		FixedIPs: []ports.IP{
   386  			{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"},
   387  		},
   388  		SecurityGroups: &[]string{"foo"},
   389  		AllowedAddressPairs: []ports.AddressPair{
   390  			{IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"},
   391  		},
   392  		ValueSpecs: &map[string]string{
   393  			"test": "value",
   394  		},
   395  	}
   396  	n, err := ports.Create(context.TODO(), fake.ServiceClient(), options).Extract()
   397  	th.AssertNoErr(t, err)
   398  
   399  	th.AssertEquals(t, n.Status, "DOWN")
   400  	th.AssertEquals(t, n.Name, "private-port")
   401  	th.AssertEquals(t, n.AdminStateUp, true)
   402  	th.AssertEquals(t, n.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7")
   403  	th.AssertEquals(t, n.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa")
   404  	th.AssertEquals(t, n.DeviceOwner, "")
   405  	th.AssertEquals(t, n.MACAddress, "fa:16:3e:c9:cb:f0")
   406  	th.AssertDeepEquals(t, n.FixedIPs, []ports.IP{
   407  		{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"},
   408  	})
   409  	th.AssertEquals(t, n.ID, "65c0ee9f-d634-4522-8954-51021b570b0d")
   410  	th.AssertDeepEquals(t, n.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"})
   411  	th.AssertDeepEquals(t, n.AllowedAddressPairs, []ports.AddressPair{
   412  		{IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"},
   413  	})
   414  }
   415  
   416  func TestCreateWithInvalidValueSpecs(t *testing.T) {
   417  	th.SetupHTTP()
   418  	defer th.TeardownHTTP()
   419  
   420  	th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) {
   421  		th.TestMethod(t, r, "POST")
   422  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   423  		th.TestHeader(t, r, "Content-Type", "application/json")
   424  		th.TestHeader(t, r, "Accept", "application/json")
   425  		th.TestJSONRequest(t, r, CreateValueSpecRequest)
   426  
   427  		w.Header().Add("Content-Type", "application/json")
   428  		w.WriteHeader(http.StatusCreated)
   429  
   430  		fmt.Fprint(w, CreateValueSpecResponse)
   431  	})
   432  
   433  	asu := true
   434  	options := ports.CreateOpts{
   435  		Name:         "private-port",
   436  		AdminStateUp: &asu,
   437  		NetworkID:    "a87cc70a-3e15-4acf-8205-9b711a3531b7",
   438  		FixedIPs: []ports.IP{
   439  			{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"},
   440  		},
   441  		SecurityGroups: &[]string{"foo"},
   442  		AllowedAddressPairs: []ports.AddressPair{
   443  			{IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"},
   444  		},
   445  		ValueSpecs: &map[string]string{
   446  			// This is a forbidden key
   447  			"shared": "value",
   448  		},
   449  	}
   450  
   451  	// We expect an error here since we used a fobidden key in the value specs.
   452  	_, err := ports.Create(context.TODO(), fake.ServiceClient(), options).Extract()
   453  	th.AssertErr(t, err)
   454  
   455  	options.ValueSpecs = &map[string]string{
   456  		// Try to overwrite an existing field
   457  		"name": "overwrite",
   458  	}
   459  
   460  	// We expect an error here since the value specs would overwrite an existing field.
   461  	_, err = ports.Create(context.TODO(), fake.ServiceClient(), options).Extract()
   462  	th.AssertErr(t, err)
   463  }
   464  
   465  func TestRequiredCreateOpts(t *testing.T) {
   466  	res := ports.Create(context.TODO(), fake.ServiceClient(), ports.CreateOpts{})
   467  	if res.Err == nil {
   468  		t.Fatalf("Expected error, got none")
   469  	}
   470  }
   471  
   472  func TestCreatePortSecurity(t *testing.T) {
   473  	th.SetupHTTP()
   474  	defer th.TeardownHTTP()
   475  
   476  	th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) {
   477  		th.TestMethod(t, r, "POST")
   478  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   479  		th.TestHeader(t, r, "Content-Type", "application/json")
   480  		th.TestHeader(t, r, "Accept", "application/json")
   481  		th.TestJSONRequest(t, r, CreatePortSecurityRequest)
   482  
   483  		w.Header().Add("Content-Type", "application/json")
   484  		w.WriteHeader(http.StatusCreated)
   485  
   486  		fmt.Fprint(w, CreatePortSecurityResponse)
   487  	})
   488  
   489  	var portWithExt struct {
   490  		ports.Port
   491  		portsecurity.PortSecurityExt
   492  	}
   493  
   494  	asu := true
   495  	iFalse := false
   496  	portCreateOpts := ports.CreateOpts{
   497  		Name:         "private-port",
   498  		AdminStateUp: &asu,
   499  		NetworkID:    "a87cc70a-3e15-4acf-8205-9b711a3531b7",
   500  		FixedIPs: []ports.IP{
   501  			{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"},
   502  		},
   503  		SecurityGroups: &[]string{"foo"},
   504  		AllowedAddressPairs: []ports.AddressPair{
   505  			{IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"},
   506  		},
   507  	}
   508  	createOpts := portsecurity.PortCreateOptsExt{
   509  		CreateOptsBuilder:   portCreateOpts,
   510  		PortSecurityEnabled: &iFalse,
   511  	}
   512  
   513  	err := ports.Create(context.TODO(), fake.ServiceClient(), createOpts).ExtractInto(&portWithExt)
   514  	th.AssertNoErr(t, err)
   515  
   516  	th.AssertEquals(t, portWithExt.Status, "DOWN")
   517  	th.AssertEquals(t, portWithExt.PortSecurityEnabled, false)
   518  }
   519  
   520  func TestUpdate(t *testing.T) {
   521  	th.SetupHTTP()
   522  	defer th.TeardownHTTP()
   523  
   524  	th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) {
   525  		th.TestMethod(t, r, "PUT")
   526  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   527  		th.TestHeader(t, r, "Content-Type", "application/json")
   528  		th.TestHeader(t, r, "Accept", "application/json")
   529  		th.TestJSONRequest(t, r, UpdateRequest)
   530  
   531  		w.Header().Add("Content-Type", "application/json")
   532  		w.WriteHeader(http.StatusOK)
   533  
   534  		fmt.Fprint(w, UpdateResponse)
   535  	})
   536  
   537  	name := "new_port_name"
   538  	options := ports.UpdateOpts{
   539  		Name: &name,
   540  		FixedIPs: []ports.IP{
   541  			{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"},
   542  		},
   543  		SecurityGroups: &[]string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"},
   544  		AllowedAddressPairs: &[]ports.AddressPair{
   545  			{IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"},
   546  		},
   547  	}
   548  
   549  	s, err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract()
   550  	th.AssertNoErr(t, err)
   551  
   552  	th.AssertEquals(t, s.Name, "new_port_name")
   553  	th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{
   554  		{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"},
   555  	})
   556  	th.AssertDeepEquals(t, s.AllowedAddressPairs, []ports.AddressPair{
   557  		{IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"},
   558  	})
   559  	th.AssertDeepEquals(t, s.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"})
   560  }
   561  
   562  func TestUpdateOmitSecurityGroups(t *testing.T) {
   563  	th.SetupHTTP()
   564  	defer th.TeardownHTTP()
   565  
   566  	th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) {
   567  		th.TestMethod(t, r, "PUT")
   568  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   569  		th.TestHeader(t, r, "Content-Type", "application/json")
   570  		th.TestHeader(t, r, "Accept", "application/json")
   571  		th.TestJSONRequest(t, r, UpdateOmitSecurityGroupsRequest)
   572  
   573  		w.Header().Add("Content-Type", "application/json")
   574  		w.WriteHeader(http.StatusOK)
   575  
   576  		fmt.Fprint(w, UpdateOmitSecurityGroupsResponse)
   577  	})
   578  
   579  	name := "new_port_name"
   580  	options := ports.UpdateOpts{
   581  		Name: &name,
   582  		FixedIPs: []ports.IP{
   583  			{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"},
   584  		},
   585  		AllowedAddressPairs: &[]ports.AddressPair{
   586  			{IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"},
   587  		},
   588  	}
   589  
   590  	s, err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract()
   591  	th.AssertNoErr(t, err)
   592  
   593  	th.AssertEquals(t, s.Name, "new_port_name")
   594  	th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{
   595  		{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"},
   596  	})
   597  	th.AssertDeepEquals(t, s.AllowedAddressPairs, []ports.AddressPair{
   598  		{IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"},
   599  	})
   600  	th.AssertDeepEquals(t, s.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"})
   601  }
   602  
   603  func TestUpdatePropagateUplinkStatus(t *testing.T) {
   604  	th.SetupHTTP()
   605  	defer th.TeardownHTTP()
   606  
   607  	th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) {
   608  		th.TestMethod(t, r, "PUT")
   609  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   610  		th.TestHeader(t, r, "Content-Type", "application/json")
   611  		th.TestHeader(t, r, "Accept", "application/json")
   612  		th.TestJSONRequest(t, r, UpdatePropagateUplinkStatusRequest)
   613  
   614  		w.Header().Add("Content-Type", "application/json")
   615  		w.WriteHeader(http.StatusOK)
   616  
   617  		fmt.Fprint(w, UpdatePropagateUplinkStatusResponse)
   618  	})
   619  
   620  	propagateUplinkStatus := true
   621  	options := ports.UpdateOpts{
   622  		PropagateUplinkStatus: &propagateUplinkStatus,
   623  	}
   624  
   625  	s, err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract()
   626  	th.AssertNoErr(t, err)
   627  
   628  	th.AssertDeepEquals(t, s.PropagateUplinkStatus, propagateUplinkStatus)
   629  }
   630  
   631  func TestUpdateValueSpecs(t *testing.T) {
   632  	th.SetupHTTP()
   633  	defer th.TeardownHTTP()
   634  
   635  	th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) {
   636  		th.TestMethod(t, r, "PUT")
   637  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   638  		th.TestHeader(t, r, "Content-Type", "application/json")
   639  		th.TestHeader(t, r, "Accept", "application/json")
   640  		th.TestJSONRequest(t, r, UpdateValueSpecsRequest)
   641  
   642  		w.Header().Add("Content-Type", "application/json")
   643  		w.WriteHeader(http.StatusOK)
   644  
   645  		fmt.Fprint(w, UpdateValueSpecsResponse)
   646  	})
   647  
   648  	options := ports.UpdateOpts{
   649  		ValueSpecs: &map[string]string{
   650  			"test": "update",
   651  		},
   652  	}
   653  
   654  	_, err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract()
   655  	th.AssertNoErr(t, err)
   656  }
   657  
   658  func TestUpdatePortSecurity(t *testing.T) {
   659  	th.SetupHTTP()
   660  	defer th.TeardownHTTP()
   661  
   662  	th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) {
   663  		th.TestMethod(t, r, "PUT")
   664  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   665  		th.TestHeader(t, r, "Content-Type", "application/json")
   666  		th.TestHeader(t, r, "Accept", "application/json")
   667  		th.TestJSONRequest(t, r, UpdatePortSecurityRequest)
   668  
   669  		w.Header().Add("Content-Type", "application/json")
   670  		w.WriteHeader(http.StatusOK)
   671  
   672  		fmt.Fprint(w, UpdatePortSecurityResponse)
   673  	})
   674  
   675  	var portWithExt struct {
   676  		ports.Port
   677  		portsecurity.PortSecurityExt
   678  	}
   679  
   680  	iFalse := false
   681  	portUpdateOpts := ports.UpdateOpts{}
   682  	updateOpts := portsecurity.PortUpdateOptsExt{
   683  		UpdateOptsBuilder:   portUpdateOpts,
   684  		PortSecurityEnabled: &iFalse,
   685  	}
   686  
   687  	err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&portWithExt)
   688  	th.AssertNoErr(t, err)
   689  
   690  	th.AssertEquals(t, portWithExt.Status, "DOWN")
   691  	th.AssertEquals(t, portWithExt.Name, "private-port")
   692  	th.AssertEquals(t, portWithExt.PortSecurityEnabled, false)
   693  }
   694  
   695  func TestUpdateRevision(t *testing.T) {
   696  	th.SetupHTTP()
   697  	defer th.TeardownHTTP()
   698  
   699  	th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) {
   700  		th.TestMethod(t, r, "PUT")
   701  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   702  		th.TestHeader(t, r, "Content-Type", "application/json")
   703  		th.TestHeader(t, r, "Accept", "application/json")
   704  		th.TestHeaderUnset(t, r, "If-Match")
   705  		th.TestJSONRequest(t, r, UpdateRequest)
   706  
   707  		w.Header().Add("Content-Type", "application/json")
   708  		w.WriteHeader(http.StatusOK)
   709  
   710  		fmt.Fprint(w, UpdateResponse)
   711  	})
   712  	th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0e", func(w http.ResponseWriter, r *http.Request) {
   713  		th.TestMethod(t, r, "PUT")
   714  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   715  		th.TestHeader(t, r, "Content-Type", "application/json")
   716  		th.TestHeader(t, r, "Accept", "application/json")
   717  		th.TestHeader(t, r, "If-Match", "revision_number=42")
   718  		th.TestJSONRequest(t, r, UpdateRequest)
   719  
   720  		w.Header().Add("Content-Type", "application/json")
   721  		w.WriteHeader(http.StatusOK)
   722  
   723  		fmt.Fprint(w, UpdateResponse)
   724  	})
   725  
   726  	name := "new_port_name"
   727  	options := ports.UpdateOpts{
   728  		Name: &name,
   729  		FixedIPs: []ports.IP{
   730  			{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"},
   731  		},
   732  		SecurityGroups: &[]string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"},
   733  		AllowedAddressPairs: &[]ports.AddressPair{
   734  			{IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"},
   735  		},
   736  	}
   737  	_, err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract()
   738  	th.AssertNoErr(t, err)
   739  
   740  	revisionNumber := 42
   741  	options.RevisionNumber = &revisionNumber
   742  	_, err = ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0e", options).Extract()
   743  	th.AssertNoErr(t, err)
   744  }
   745  
   746  func TestRemoveSecurityGroups(t *testing.T) {
   747  	th.SetupHTTP()
   748  	defer th.TeardownHTTP()
   749  
   750  	th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) {
   751  		th.TestMethod(t, r, "PUT")
   752  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   753  		th.TestHeader(t, r, "Content-Type", "application/json")
   754  		th.TestHeader(t, r, "Accept", "application/json")
   755  		th.TestJSONRequest(t, r, RemoveSecurityGroupRequest)
   756  
   757  		w.Header().Add("Content-Type", "application/json")
   758  		w.WriteHeader(http.StatusOK)
   759  
   760  		fmt.Fprint(w, RemoveSecurityGroupResponse)
   761  	})
   762  
   763  	name := "new_port_name"
   764  	options := ports.UpdateOpts{
   765  		Name: &name,
   766  		FixedIPs: []ports.IP{
   767  			{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"},
   768  		},
   769  		SecurityGroups: &[]string{},
   770  		AllowedAddressPairs: &[]ports.AddressPair{
   771  			{IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"},
   772  		},
   773  	}
   774  
   775  	s, err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract()
   776  	th.AssertNoErr(t, err)
   777  
   778  	th.AssertEquals(t, s.Name, "new_port_name")
   779  	th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{
   780  		{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"},
   781  	})
   782  	th.AssertDeepEquals(t, s.AllowedAddressPairs, []ports.AddressPair{
   783  		{IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"},
   784  	})
   785  	th.AssertDeepEquals(t, s.SecurityGroups, []string(nil))
   786  }
   787  
   788  func TestRemoveAllowedAddressPairs(t *testing.T) {
   789  	th.SetupHTTP()
   790  	defer th.TeardownHTTP()
   791  
   792  	th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) {
   793  		th.TestMethod(t, r, "PUT")
   794  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   795  		th.TestHeader(t, r, "Content-Type", "application/json")
   796  		th.TestHeader(t, r, "Accept", "application/json")
   797  		th.TestJSONRequest(t, r, RemoveAllowedAddressPairsRequest)
   798  
   799  		w.Header().Add("Content-Type", "application/json")
   800  		w.WriteHeader(http.StatusOK)
   801  
   802  		fmt.Fprint(w, RemoveAllowedAddressPairsResponse)
   803  	})
   804  
   805  	name := "new_port_name"
   806  	options := ports.UpdateOpts{
   807  		Name: &name,
   808  		FixedIPs: []ports.IP{
   809  			{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"},
   810  		},
   811  		SecurityGroups:      &[]string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"},
   812  		AllowedAddressPairs: &[]ports.AddressPair{},
   813  	}
   814  
   815  	s, err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract()
   816  	th.AssertNoErr(t, err)
   817  
   818  	th.AssertEquals(t, s.Name, "new_port_name")
   819  	th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{
   820  		{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"},
   821  	})
   822  	th.AssertDeepEquals(t, s.AllowedAddressPairs, []ports.AddressPair(nil))
   823  	th.AssertDeepEquals(t, s.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"})
   824  }
   825  
   826  func TestDontUpdateAllowedAddressPairs(t *testing.T) {
   827  	th.SetupHTTP()
   828  	defer th.TeardownHTTP()
   829  
   830  	th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) {
   831  		th.TestMethod(t, r, "PUT")
   832  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   833  		th.TestHeader(t, r, "Content-Type", "application/json")
   834  		th.TestHeader(t, r, "Accept", "application/json")
   835  		th.TestJSONRequest(t, r, DontUpdateAllowedAddressPairsRequest)
   836  
   837  		w.Header().Add("Content-Type", "application/json")
   838  		w.WriteHeader(http.StatusOK)
   839  
   840  		fmt.Fprint(w, DontUpdateAllowedAddressPairsResponse)
   841  	})
   842  
   843  	name := "new_port_name"
   844  	options := ports.UpdateOpts{
   845  		Name: &name,
   846  		FixedIPs: []ports.IP{
   847  			{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"},
   848  		},
   849  		SecurityGroups: &[]string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"},
   850  	}
   851  
   852  	s, err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract()
   853  	th.AssertNoErr(t, err)
   854  
   855  	th.AssertEquals(t, s.Name, "new_port_name")
   856  	th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{
   857  		{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"},
   858  	})
   859  	th.AssertDeepEquals(t, s.AllowedAddressPairs, []ports.AddressPair{
   860  		{IPAddress: "10.0.0.4", MACAddress: "fa:16:3e:c9:cb:f0"},
   861  	})
   862  	th.AssertDeepEquals(t, s.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"})
   863  }
   864  
   865  func TestDelete(t *testing.T) {
   866  	th.SetupHTTP()
   867  	defer th.TeardownHTTP()
   868  
   869  	th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) {
   870  		th.TestMethod(t, r, "DELETE")
   871  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   872  		w.WriteHeader(http.StatusNoContent)
   873  	})
   874  
   875  	res := ports.Delete(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d")
   876  	th.AssertNoErr(t, res.Err)
   877  }
   878  
   879  func TestGetWithExtraDHCPOpts(t *testing.T) {
   880  	th.SetupHTTP()
   881  	defer th.TeardownHTTP()
   882  
   883  	th.Mux.HandleFunc("/v2.0/ports/46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", func(w http.ResponseWriter, r *http.Request) {
   884  		th.TestMethod(t, r, "GET")
   885  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   886  
   887  		w.Header().Add("Content-Type", "application/json")
   888  		w.WriteHeader(http.StatusOK)
   889  
   890  		fmt.Fprint(w, GetWithExtraDHCPOptsResponse)
   891  	})
   892  
   893  	var s struct {
   894  		ports.Port
   895  		extradhcpopts.ExtraDHCPOptsExt
   896  	}
   897  
   898  	err := ports.Get(context.TODO(), fake.ServiceClient(), "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2").ExtractInto(&s)
   899  	th.AssertNoErr(t, err)
   900  
   901  	th.AssertEquals(t, s.Status, "ACTIVE")
   902  	th.AssertEquals(t, s.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7")
   903  	th.AssertEquals(t, s.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa")
   904  	th.AssertEquals(t, s.AdminStateUp, true)
   905  	th.AssertEquals(t, s.Name, "port-with-extra-dhcp-opts")
   906  	th.AssertEquals(t, s.DeviceOwner, "")
   907  	th.AssertEquals(t, s.MACAddress, "fa:16:3e:c9:cb:f0")
   908  	th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{
   909  		{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.4"},
   910  	})
   911  	th.AssertEquals(t, s.ID, "65c0ee9f-d634-4522-8954-51021b570b0d")
   912  	th.AssertEquals(t, s.DeviceID, "")
   913  
   914  	th.AssertDeepEquals(t, s.ExtraDHCPOpts[0].OptName, "option1")
   915  	th.AssertDeepEquals(t, s.ExtraDHCPOpts[0].OptValue, "value1")
   916  	th.AssertDeepEquals(t, s.ExtraDHCPOpts[0].IPVersion, 4)
   917  	th.AssertDeepEquals(t, s.ExtraDHCPOpts[1].OptName, "option2")
   918  	th.AssertDeepEquals(t, s.ExtraDHCPOpts[1].OptValue, "value2")
   919  	th.AssertDeepEquals(t, s.ExtraDHCPOpts[1].IPVersion, 4)
   920  }
   921  
   922  func TestCreateWithExtraDHCPOpts(t *testing.T) {
   923  	th.SetupHTTP()
   924  	defer th.TeardownHTTP()
   925  
   926  	th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) {
   927  		th.TestMethod(t, r, "POST")
   928  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   929  		th.TestHeader(t, r, "Content-Type", "application/json")
   930  		th.TestHeader(t, r, "Accept", "application/json")
   931  		th.TestJSONRequest(t, r, CreateWithExtraDHCPOptsRequest)
   932  
   933  		w.Header().Add("Content-Type", "application/json")
   934  		w.WriteHeader(http.StatusCreated)
   935  
   936  		fmt.Fprint(w, CreateWithExtraDHCPOptsResponse)
   937  	})
   938  
   939  	adminStateUp := true
   940  	portCreateOpts := ports.CreateOpts{
   941  		Name:         "port-with-extra-dhcp-opts",
   942  		AdminStateUp: &adminStateUp,
   943  		NetworkID:    "a87cc70a-3e15-4acf-8205-9b711a3531b7",
   944  		FixedIPs: []ports.IP{
   945  			{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"},
   946  		},
   947  	}
   948  
   949  	createOpts := extradhcpopts.CreateOptsExt{
   950  		CreateOptsBuilder: portCreateOpts,
   951  		ExtraDHCPOpts: []extradhcpopts.CreateExtraDHCPOpt{
   952  			{
   953  				OptName:  "option1",
   954  				OptValue: "value1",
   955  			},
   956  		},
   957  	}
   958  
   959  	var s struct {
   960  		ports.Port
   961  		extradhcpopts.ExtraDHCPOptsExt
   962  	}
   963  
   964  	err := ports.Create(context.TODO(), fake.ServiceClient(), createOpts).ExtractInto(&s)
   965  	th.AssertNoErr(t, err)
   966  
   967  	th.AssertEquals(t, s.Status, "DOWN")
   968  	th.AssertEquals(t, s.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7")
   969  	th.AssertEquals(t, s.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa")
   970  	th.AssertEquals(t, s.AdminStateUp, true)
   971  	th.AssertEquals(t, s.Name, "port-with-extra-dhcp-opts")
   972  	th.AssertEquals(t, s.DeviceOwner, "")
   973  	th.AssertEquals(t, s.MACAddress, "fa:16:3e:c9:cb:f0")
   974  	th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{
   975  		{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"},
   976  	})
   977  	th.AssertEquals(t, s.ID, "65c0ee9f-d634-4522-8954-51021b570b0d")
   978  	th.AssertEquals(t, s.DeviceID, "")
   979  
   980  	th.AssertDeepEquals(t, s.ExtraDHCPOpts[0].OptName, "option1")
   981  	th.AssertDeepEquals(t, s.ExtraDHCPOpts[0].OptValue, "value1")
   982  	th.AssertDeepEquals(t, s.ExtraDHCPOpts[0].IPVersion, 4)
   983  }
   984  
   985  func TestUpdateWithExtraDHCPOpts(t *testing.T) {
   986  	th.SetupHTTP()
   987  	defer th.TeardownHTTP()
   988  
   989  	th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) {
   990  		th.TestMethod(t, r, "PUT")
   991  		th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
   992  		th.TestHeader(t, r, "Content-Type", "application/json")
   993  		th.TestHeader(t, r, "Accept", "application/json")
   994  		th.TestJSONRequest(t, r, UpdateWithExtraDHCPOptsRequest)
   995  
   996  		w.Header().Add("Content-Type", "application/json")
   997  		w.WriteHeader(http.StatusOK)
   998  
   999  		fmt.Fprint(w, UpdateWithExtraDHCPOptsResponse)
  1000  	})
  1001  
  1002  	name := "updated-port-with-dhcp-opts"
  1003  	portUpdateOpts := ports.UpdateOpts{
  1004  		Name: &name,
  1005  		FixedIPs: []ports.IP{
  1006  			{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"},
  1007  		},
  1008  	}
  1009  
  1010  	edoValue2 := "value2"
  1011  	updateOpts := extradhcpopts.UpdateOptsExt{
  1012  		UpdateOptsBuilder: portUpdateOpts,
  1013  		ExtraDHCPOpts: []extradhcpopts.UpdateExtraDHCPOpt{
  1014  			{
  1015  				OptName: "option1",
  1016  			},
  1017  			{
  1018  				OptName:  "option2",
  1019  				OptValue: &edoValue2,
  1020  			},
  1021  		},
  1022  	}
  1023  
  1024  	var s struct {
  1025  		ports.Port
  1026  		extradhcpopts.ExtraDHCPOptsExt
  1027  	}
  1028  
  1029  	err := ports.Update(context.TODO(), fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", updateOpts).ExtractInto(&s)
  1030  	th.AssertNoErr(t, err)
  1031  
  1032  	th.AssertEquals(t, s.Status, "DOWN")
  1033  	th.AssertEquals(t, s.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7")
  1034  	th.AssertEquals(t, s.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa")
  1035  	th.AssertEquals(t, s.AdminStateUp, true)
  1036  	th.AssertEquals(t, s.Name, "updated-port-with-dhcp-opts")
  1037  	th.AssertEquals(t, s.DeviceOwner, "")
  1038  	th.AssertEquals(t, s.MACAddress, "fa:16:3e:c9:cb:f0")
  1039  	th.AssertDeepEquals(t, s.FixedIPs, []ports.IP{
  1040  		{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"},
  1041  	})
  1042  	th.AssertEquals(t, s.ID, "65c0ee9f-d634-4522-8954-51021b570b0d")
  1043  	th.AssertEquals(t, s.DeviceID, "")
  1044  
  1045  	th.AssertDeepEquals(t, s.ExtraDHCPOpts[0].OptName, "option2")
  1046  	th.AssertDeepEquals(t, s.ExtraDHCPOpts[0].OptValue, "value2")
  1047  	th.AssertDeepEquals(t, s.ExtraDHCPOpts[0].IPVersion, 4)
  1048  }
  1049  
  1050  func TestPortsListOpts(t *testing.T) {
  1051  	for _, tt := range []struct {
  1052  		listOpts ports.ListOpts
  1053  		params   []struct{ key, value string }
  1054  	}{
  1055  		{
  1056  			listOpts: ports.ListOpts{
  1057  				FixedIPs: []ports.FixedIPOpts{{IPAddress: "1.2.3.4", SubnetID: "42"}},
  1058  			},
  1059  			params: []struct {
  1060  				key   string
  1061  				value string
  1062  			}{
  1063  				{"fixed_ips", "ip_address=1.2.3.4"},
  1064  				{"fixed_ips", "subnet_id=42"},
  1065  			},
  1066  		},
  1067  	} {
  1068  		v := url.Values{}
  1069  		for _, param := range tt.params {
  1070  			v.Add(param.key, param.value)
  1071  		}
  1072  		expected := "?" + v.Encode()
  1073  
  1074  		actual, _ := tt.listOpts.ToPortListQuery()
  1075  		th.AssertEquals(t, expected, actual)
  1076  	}
  1077  }