gopkg.in/goose.v2@v2.0.1/testservices/neutronservice/service_http_test.go (about)

     1  // Neutron double testing service - HTTP API tests
     2  
     3  package neutronservice
     4  
     5  import (
     6  	"bytes"
     7  	"encoding/json"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"strconv"
    12  	"strings"
    13  
    14  	gc "gopkg.in/check.v1"
    15  
    16  	"gopkg.in/goose.v2/neutron"
    17  	"gopkg.in/goose.v2/testing/httpsuite"
    18  	"gopkg.in/goose.v2/testservices/identityservice"
    19  	"gopkg.in/goose.v2/testservices/neutronmodel"
    20  )
    21  
    22  type NeutronHTTPSuite struct {
    23  	httpsuite.HTTPSuite
    24  	service *Neutron
    25  	token   string
    26  }
    27  
    28  var _ = gc.Suite(&NeutronHTTPSuite{})
    29  
    30  type NeutronHTTPSSuite struct {
    31  	httpsuite.HTTPSuite
    32  	service *Neutron
    33  	token   string
    34  }
    35  
    36  var _ = gc.Suite(&NeutronHTTPSSuite{HTTPSuite: httpsuite.HTTPSuite{UseTLS: true}})
    37  
    38  func (s *NeutronHTTPSuite) SetUpSuite(c *gc.C) {
    39  	s.HTTPSuite.SetUpSuite(c)
    40  	identityDouble := identityservice.NewUserPass()
    41  	userInfo := identityDouble.AddUser("fred", "secret", "tenant", "default")
    42  	s.token = userInfo.Token
    43  	s.service = New(s.Server.URL, versionPath, userInfo.TenantId, region, identityDouble, nil)
    44  	s.service.AddNeutronModel(neutronmodel.New())
    45  }
    46  
    47  func (s *NeutronHTTPSuite) TearDownSuite(c *gc.C) {
    48  	s.HTTPSuite.TearDownSuite(c)
    49  }
    50  
    51  func (s *NeutronHTTPSuite) SetUpTest(c *gc.C) {
    52  	s.HTTPSuite.SetUpTest(c)
    53  	s.service.SetupHTTP(s.Mux)
    54  	// this is otherwise handled not directly by neutron test service
    55  	// but by openstack that tries for / before.
    56  	s.Mux.Handle("/", s.service.handler((*Neutron).handleRoot))
    57  }
    58  
    59  func (s *NeutronHTTPSuite) TearDownTest(c *gc.C) {
    60  	s.HTTPSuite.TearDownTest(c)
    61  }
    62  
    63  // assertJSON asserts the passed http.Response's body can be
    64  // unmarshalled into the given expected object, populating it with the
    65  // successfully parsed data.
    66  func assertJSON(c *gc.C, resp *http.Response, expected interface{}) {
    67  	body, err := ioutil.ReadAll(resp.Body)
    68  	defer resp.Body.Close()
    69  	c.Assert(err, gc.IsNil)
    70  	err = json.Unmarshal(body, &expected)
    71  	c.Assert(err, gc.IsNil)
    72  }
    73  
    74  // assertBody asserts the passed http.Response's body matches the
    75  // expected response, replacing any variables in the expected body.
    76  func assertBody(c *gc.C, resp *http.Response, expected *errorResponse) {
    77  	body, err := ioutil.ReadAll(resp.Body)
    78  	defer resp.Body.Close()
    79  	c.Assert(err, gc.IsNil)
    80  	expBody := expected.requestBody(resp.Request)
    81  	// cast to string for easier asserts debugging
    82  	c.Assert(string(body), gc.Equals, string(expBody))
    83  }
    84  
    85  // sendRequest constructs an HTTP request from the parameters and
    86  // sends it, returning the response or an error.
    87  func (s *NeutronHTTPSuite) sendRequest(method, url string, body []byte, headers http.Header) (*http.Response, error) {
    88  	if !strings.HasPrefix(url, "http") {
    89  		url = "http://" + s.service.Hostname + strings.TrimLeft(url, "/")
    90  	}
    91  	req, err := http.NewRequest(method, url, bytes.NewReader(body))
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	for header, values := range headers {
    96  		for _, value := range values {
    97  			req.Header.Add(header, value)
    98  		}
    99  	}
   100  	// workaround for https://code.google.com/p/go/issues/detail?id=4454
   101  	req.Header.Set("Content-Length", strconv.Itoa(len(body)))
   102  	return http.DefaultClient.Do(req)
   103  }
   104  
   105  // authRequest is a shortcut for sending requests with pre-set token
   106  // header and correct version prefix and tenant ID in the URL.
   107  func (s *NeutronHTTPSuite) authRequest(method, path string, body []byte, headers http.Header) (*http.Response, error) {
   108  	if headers == nil {
   109  		headers = make(http.Header)
   110  	}
   111  	headers.Set(authToken, s.token)
   112  	url := s.service.endpointURL(true, path)
   113  	return s.sendRequest(method, url, body, headers)
   114  }
   115  
   116  // jsonRequest serializes the passed body object to JSON and sends a
   117  // the request with authRequest().
   118  func (s *NeutronHTTPSuite) jsonRequest(method, path string, body interface{}, headers http.Header) (*http.Response, error) {
   119  	jsonBody, err := json.Marshal(body)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	return s.authRequest(method, path, jsonBody, headers)
   124  }
   125  
   126  // setHeader creates http.Header map, sets the given header, and
   127  // returns the map.
   128  func setHeader(header, value string) http.Header {
   129  	h := make(http.Header)
   130  	h.Set(header, value)
   131  	return h
   132  }
   133  
   134  // SimpleTest defines a simple request without a body and expected response.
   135  type SimpleTest struct {
   136  	unauth  bool
   137  	method  string
   138  	url     string
   139  	headers http.Header
   140  	expect  *errorResponse
   141  }
   142  
   143  func (s *NeutronHTTPSuite) simpleTests() []SimpleTest {
   144  	var simpleTests = []SimpleTest{
   145  		{
   146  			unauth:  true,
   147  			method:  "GET",
   148  			url:     "/any",
   149  			headers: make(http.Header),
   150  			expect:  errUnauthorized,
   151  		},
   152  		{
   153  			unauth:  true,
   154  			method:  "POST",
   155  			url:     "/any",
   156  			headers: setHeader(authToken, "phony"),
   157  			expect:  errUnauthorized,
   158  		},
   159  		{
   160  			unauth:  true,
   161  			method:  "GET",
   162  			url:     "/any",
   163  			headers: setHeader(authToken, s.token),
   164  			expect:  errMultipleChoices,
   165  		},
   166  		{
   167  			unauth:  true,
   168  			method:  "POST",
   169  			url:     "/any/unknown/one",
   170  			headers: setHeader(authToken, s.token),
   171  			expect:  errMultipleChoices,
   172  		},
   173  		{
   174  			unauth:  true,
   175  			method:  "GET",
   176  			url:     versionPath + "/phony_token",
   177  			headers: setHeader(authToken, s.token),
   178  			expect:  errBadRequestMalformedURL,
   179  		},
   180  
   181  		{
   182  			method: "GET",
   183  			url:    neutron.ApiSecurityGroupsV2 + "/42",
   184  			expect: errNotFoundJSONSG,
   185  		},
   186  		{
   187  			method: "POST",
   188  			url:    neutron.ApiSecurityGroupsV2,
   189  			expect: errBadRequestIncorrect,
   190  		},
   191  		{
   192  			method: "POST",
   193  			url:    neutron.ApiSecurityGroupsV2 + "/invalid",
   194  			expect: errNotFound,
   195  		},
   196  		{
   197  			method: "PUT",
   198  			url:    neutron.ApiSecurityGroupsV2,
   199  			expect: errNotFound,
   200  		},
   201  		{
   202  			method: "DELETE",
   203  			url:    neutron.ApiSecurityGroupsV2,
   204  			expect: errNotFound,
   205  		},
   206  		{
   207  			method: "DELETE",
   208  			url:    neutron.ApiSecurityGroupsV2 + "/42",
   209  			expect: errNotFoundJSONSG,
   210  		},
   211  
   212  		{
   213  			method: "GET",
   214  			url:    neutron.ApiSecurityGroupRulesV2,
   215  			expect: errNotFoundJSON,
   216  		},
   217  		{
   218  			method: "GET",
   219  			url:    neutron.ApiSecurityGroupRulesV2 + "/invalid",
   220  			expect: errNotFoundJSON,
   221  		},
   222  		{
   223  			method: "GET",
   224  			url:    neutron.ApiSecurityGroupRulesV2 + "/42",
   225  			expect: errNotFoundJSON,
   226  		},
   227  		{
   228  			method: "POST",
   229  			url:    neutron.ApiSecurityGroupRulesV2,
   230  			expect: errBadRequestIncorrect,
   231  		},
   232  		{
   233  			method: "POST",
   234  			url:    neutron.ApiSecurityGroupRulesV2 + "/invalid",
   235  			expect: errNotFound,
   236  		},
   237  		{
   238  			method: "PUT",
   239  			url:    neutron.ApiSecurityGroupRulesV2,
   240  			expect: errNotFound,
   241  		},
   242  		{
   243  			method: "PUT",
   244  			url:    neutron.ApiSecurityGroupRulesV2 + "/invalid",
   245  			expect: errNotFound,
   246  		},
   247  		{
   248  			method: "DELETE",
   249  			url:    neutron.ApiSecurityGroupRulesV2,
   250  			expect: errNotFound,
   251  		},
   252  		{
   253  			method: "DELETE",
   254  			url:    neutron.ApiSecurityGroupRulesV2 + "/42",
   255  			expect: errNotFoundJSONSGR,
   256  		},
   257  
   258  		{
   259  			method: "GET",
   260  			url:    neutron.ApiFloatingIPsV2 + "/42",
   261  			expect: errNotFoundJSON,
   262  		},
   263  		{
   264  			method: "POST",
   265  			url:    neutron.ApiFloatingIPsV2 + "/invalid",
   266  			expect: errNotFound,
   267  		},
   268  		{
   269  			method: "PUT",
   270  			url:    neutron.ApiFloatingIPsV2,
   271  			expect: errNotFound,
   272  		},
   273  		{
   274  			method: "PUT",
   275  			url:    neutron.ApiFloatingIPsV2 + "/invalid",
   276  			expect: errNotFound,
   277  		},
   278  		{
   279  			method: "DELETE",
   280  			url:    neutron.ApiFloatingIPsV2,
   281  			expect: errNotFound,
   282  		},
   283  		{
   284  			method: "DELETE",
   285  			url:    neutron.ApiFloatingIPsV2 + "/invalid",
   286  			expect: errNotFoundJSON,
   287  		},
   288  		{
   289  			method: "GET",
   290  			url:    neutron.ApiNetworksV2 + "/42",
   291  			expect: errNotFoundJSON,
   292  		},
   293  		{
   294  			method: "POST",
   295  			url:    neutron.ApiNetworksV2 + "/invalid",
   296  			expect: errNotFound,
   297  		},
   298  		{
   299  			method: "PUT",
   300  			url:    neutron.ApiNetworksV2,
   301  			expect: errNotFound,
   302  		},
   303  		{
   304  			method: "PUT",
   305  			url:    neutron.ApiNetworksV2 + "/invalid",
   306  			expect: errNotFound,
   307  		},
   308  		{
   309  			method: "DELETE",
   310  			url:    neutron.ApiNetworksV2,
   311  			expect: errNotFound,
   312  		},
   313  		{
   314  			method: "DELETE",
   315  			url:    neutron.ApiNetworksV2 + "/invalid",
   316  			expect: errNotFound,
   317  		},
   318  
   319  		{
   320  			method: "GET",
   321  			url:    neutron.ApiSubnetsV2 + "/42",
   322  			expect: errNotFoundJSON,
   323  		},
   324  		{
   325  			method: "POST",
   326  			url:    neutron.ApiSubnetsV2 + "/invalid",
   327  			expect: errNotFound,
   328  		},
   329  		{
   330  			method: "PUT",
   331  			url:    neutron.ApiSubnetsV2,
   332  			expect: errNotFound,
   333  		},
   334  		{
   335  			method: "PUT",
   336  			url:    neutron.ApiSubnetsV2 + "/invalid",
   337  			expect: errNotFound,
   338  		},
   339  		{
   340  			method: "DELETE",
   341  			url:    neutron.ApiSubnetsV2,
   342  			expect: errNotFound,
   343  		},
   344  		{
   345  			method: "DELETE",
   346  			url:    neutron.ApiSubnetsV2 + "/invalid",
   347  			expect: errNotFound,
   348  		},
   349  
   350  		{
   351  			method: "GET",
   352  			url:    neutron.ApiPortsV2 + "/42",
   353  			expect: errNotFoundJSONP,
   354  		},
   355  		{
   356  			method: "POST",
   357  			url:    neutron.ApiPortsV2,
   358  			expect: errBadRequestIncorrect,
   359  		},
   360  		{
   361  			method: "POST",
   362  			url:    neutron.ApiPortsV2 + "/invalid",
   363  			expect: errNotFound,
   364  		},
   365  		{
   366  			method: "DELETE",
   367  			url:    neutron.ApiPortsV2,
   368  			expect: errNotFound,
   369  		},
   370  		{
   371  			method: "DELETE",
   372  			url:    neutron.ApiPortsV2 + "/42",
   373  			expect: errNotFoundJSONP,
   374  		},
   375  	}
   376  	return simpleTests
   377  }
   378  
   379  func (s *NeutronHTTPSuite) TestSimpleRequestTests(c *gc.C) {
   380  	simpleTests := s.simpleTests()
   381  	for i, t := range simpleTests {
   382  		c.Logf("#%d. %s %s -> %d", i, t.method, t.url, t.expect.code)
   383  		if t.headers == nil {
   384  			t.headers = make(http.Header)
   385  			t.headers.Set(authToken, s.token)
   386  		}
   387  		var (
   388  			resp *http.Response
   389  			err  error
   390  		)
   391  		if t.unauth {
   392  			resp, err = s.sendRequest(t.method, t.url, nil, t.headers)
   393  		} else {
   394  			resp, err = s.authRequest(t.method, t.url, nil, t.headers)
   395  		}
   396  		c.Assert(err, gc.IsNil)
   397  		c.Assert(resp.StatusCode, gc.Equals, t.expect.code)
   398  		assertBody(c, resp, t.expect)
   399  	}
   400  	fmt.Printf("total: %d\n", len(simpleTests))
   401  }
   402  
   403  func (s *NeutronHTTPSuite) TestNewUUID(c *gc.C) {
   404  	uuid, err := newUUID()
   405  	c.Assert(err, gc.IsNil)
   406  	var p1, p2, p3, p4, p5 string
   407  	num, err := fmt.Sscanf(uuid, "%8x-%4x-%4x-%4x-%12x", &p1, &p2, &p3, &p4, &p5)
   408  	c.Assert(err, gc.IsNil)
   409  	c.Assert(num, gc.Equals, 5)
   410  	uuid2, err := newUUID()
   411  	c.Assert(err, gc.IsNil)
   412  	c.Assert(uuid2, gc.Not(gc.Equals), uuid)
   413  }
   414  
   415  func (s *NeutronHTTPSuite) TestGetSecurityGroups(c *gc.C) {
   416  	// There is always a default security group.
   417  	groups := s.service.allSecurityGroups()
   418  	c.Assert(groups, gc.HasLen, 1)
   419  	var expected struct {
   420  		Groups []neutron.SecurityGroupV2 `json:"security_groups"`
   421  	}
   422  	resp, err := s.authRequest("GET", neutron.ApiSecurityGroupsV2, nil, nil)
   423  	c.Assert(err, gc.IsNil)
   424  	c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)
   425  	assertJSON(c, resp, &expected)
   426  	c.Assert(expected.Groups, gc.HasLen, 1)
   427  	groups = []neutron.SecurityGroupV2{
   428  		{
   429  			Id:       "1",
   430  			Name:     "group 1",
   431  			TenantId: s.service.TenantId,
   432  			Rules:    []neutron.SecurityGroupRuleV2{},
   433  		},
   434  		{
   435  			Id:       "2",
   436  			Name:     "group 2",
   437  			TenantId: s.service.TenantId,
   438  			Rules:    []neutron.SecurityGroupRuleV2{},
   439  		},
   440  	}
   441  	for _, group := range groups {
   442  		err := s.service.addSecurityGroup(group)
   443  		c.Assert(err, gc.IsNil)
   444  		defer s.service.removeSecurityGroup(group.Id)
   445  	}
   446  	groups[0].Rules = defaultSecurityGroupRules(groups[0].Id, groups[0].TenantId)
   447  	groups[1].Rules = defaultSecurityGroupRules(groups[1].Id, groups[1].TenantId)
   448  	resp, err = s.authRequest("GET", neutron.ApiSecurityGroupsV2, nil, nil)
   449  	c.Assert(err, gc.IsNil)
   450  	c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)
   451  	assertJSON(c, resp, &expected)
   452  	c.Assert(expected.Groups, gc.HasLen, len(groups)+1)
   453  	checkGroupsInList(c, groups, expected.Groups)
   454  	var expectedGroup struct {
   455  		Group neutron.SecurityGroupV2 `json:"security_group"`
   456  	}
   457  	url := fmt.Sprintf("%s/%s", neutron.ApiSecurityGroupsV2, "1")
   458  	resp, err = s.authRequest("GET", url, nil, nil)
   459  	c.Assert(err, gc.IsNil)
   460  	c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)
   461  	assertJSON(c, resp, &expectedGroup)
   462  	c.Assert(expectedGroup.Group, gc.DeepEquals, groups[0])
   463  }
   464  
   465  func defaultSecurityGroupRules(groupId, tenantId string) []neutron.SecurityGroupRuleV2 {
   466  	id, _ := strconv.Atoi(groupId)
   467  	id1 := id * 999
   468  	id2 := id * 998
   469  	return []neutron.SecurityGroupRuleV2{
   470  		{
   471  			Direction:     "egress",
   472  			EthernetType:  "IPv4",
   473  			Id:            strconv.Itoa(id1),
   474  			TenantId:      tenantId,
   475  			ParentGroupId: groupId,
   476  		},
   477  		{
   478  			Direction:     "egress",
   479  			EthernetType:  "IPv6",
   480  			Id:            strconv.Itoa(id2),
   481  			TenantId:      tenantId,
   482  			ParentGroupId: groupId,
   483  		},
   484  	}
   485  }
   486  
   487  func (s *NeutronHTTPSuite) TestAddSecurityGroup(c *gc.C) {
   488  	group := neutron.SecurityGroupV2{
   489  		Id:          "1",
   490  		Name:        "group 1",
   491  		Description: "desc",
   492  		TenantId:    s.service.TenantId,
   493  		Rules:       []neutron.SecurityGroupRuleV2{},
   494  	}
   495  	_, err := s.service.securityGroup(group.Id)
   496  	c.Assert(err, gc.NotNil)
   497  	group.Rules = defaultSecurityGroupRules(group.Id, group.TenantId)
   498  	var req struct {
   499  		Group struct {
   500  			Name        string `json:"name"`
   501  			Description string `json:"description"`
   502  		} `json:"security_group"`
   503  	}
   504  	req.Group.Name = group.Name
   505  	req.Group.Description = group.Description
   506  	var expected struct {
   507  		Group neutron.SecurityGroupV2 `json:"security_group"`
   508  	}
   509  	resp, err := s.jsonRequest("POST", neutron.ApiSecurityGroupsV2, req, nil)
   510  	c.Assert(err, gc.IsNil)
   511  	c.Assert(resp.StatusCode, gc.Equals, http.StatusCreated)
   512  	assertJSON(c, resp, &expected)
   513  	c.Assert(expected.Group, gc.DeepEquals, group)
   514  	err = s.service.removeSecurityGroup(group.Id)
   515  	c.Assert(err, gc.IsNil)
   516  }
   517  
   518  func (s *NeutronHTTPSuite) TestDeleteSecurityGroup(c *gc.C) {
   519  	group := neutron.SecurityGroupV2{Id: "1", Name: "group 1", TenantId: s.service.TenantId}
   520  	_, err := s.service.securityGroup(group.Id)
   521  	c.Assert(err, gc.NotNil)
   522  	err = s.service.addSecurityGroup(group)
   523  	c.Assert(err, gc.IsNil)
   524  	defer s.service.removeSecurityGroup(group.Id)
   525  	url := fmt.Sprintf("%s/%s", neutron.ApiSecurityGroupsV2, "1")
   526  	resp, err := s.authRequest("DELETE", url, nil, nil)
   527  	c.Assert(err, gc.IsNil)
   528  	c.Assert(resp.StatusCode, gc.Equals, http.StatusNoContent)
   529  	_, err = s.service.securityGroup(group.Id)
   530  	c.Assert(err, gc.NotNil)
   531  }
   532  
   533  func (s *NeutronHTTPSuite) TestAddSecurityGroupRule(c *gc.C) {
   534  	group1 := neutron.SecurityGroupV2{Id: "1", Name: "src", TenantId: s.service.TenantId}
   535  	group2 := neutron.SecurityGroupV2{Id: "2", Name: "tgt", TenantId: s.service.TenantId}
   536  	err := s.service.addSecurityGroup(group1)
   537  	c.Assert(err, gc.IsNil)
   538  	defer s.service.removeSecurityGroup(group1.Id)
   539  	err = s.service.addSecurityGroup(group2)
   540  	c.Assert(err, gc.IsNil)
   541  	defer s.service.removeSecurityGroup(group2.Id)
   542  	riIngress := neutron.RuleInfoV2{
   543  		ParentGroupId:  "1",
   544  		Direction:      "ingress",
   545  		PortRangeMax:   22,
   546  		PortRangeMin:   22,
   547  		IPProtocol:     "tcp",
   548  		RemoteIPPrefix: "1.2.3.4/5",
   549  	}
   550  	riIngress2 := neutron.RuleInfoV2{
   551  		ParentGroupId:  "1",
   552  		Direction:      "ingress",
   553  		PortRangeMax:   22,
   554  		PortRangeMin:   22,
   555  		IPProtocol:     "tcp",
   556  		RemoteIPPrefix: "2.3.4.5/6",
   557  	}
   558  	riEgress := neutron.RuleInfoV2{
   559  		ParentGroupId:  group2.Id,
   560  		Direction:      "egress",
   561  		PortRangeMax:   22,
   562  		PortRangeMin:   22,
   563  		IPProtocol:     "tcp",
   564  		RemoteIPPrefix: "5.4.3.2/1",
   565  	}
   566  	riIngress6 := neutron.RuleInfoV2{
   567  		ParentGroupId:  "1",
   568  		Direction:      "ingress",
   569  		PortRangeMax:   22,
   570  		PortRangeMin:   22,
   571  		IPProtocol:     "tcp",
   572  		RemoteIPPrefix: "2001:db8:42::/64",
   573  		EthernetType:   "IPv6",
   574  	}
   575  	rule1 := neutron.SecurityGroupRuleV2{
   576  		Id:             "1",
   577  		ParentGroupId:  group1.Id,
   578  		Direction:      riIngress.Direction,
   579  		PortRangeMax:   &riIngress.PortRangeMax,
   580  		PortRangeMin:   &riIngress.PortRangeMin,
   581  		IPProtocol:     &riIngress.IPProtocol,
   582  		RemoteIPPrefix: riIngress.RemoteIPPrefix,
   583  	}
   584  	rule2 := neutron.SecurityGroupRuleV2{
   585  		Id:             "2",
   586  		ParentGroupId:  group1.Id,
   587  		Direction:      riIngress2.Direction,
   588  		PortRangeMax:   &riIngress2.PortRangeMax,
   589  		PortRangeMin:   &riIngress2.PortRangeMin,
   590  		IPProtocol:     &riIngress2.IPProtocol,
   591  		RemoteIPPrefix: riIngress2.RemoteIPPrefix,
   592  	}
   593  	rule3 := neutron.SecurityGroupRuleV2{
   594  		Id:            "3",
   595  		ParentGroupId: group2.Id,
   596  		Direction:     riEgress.Direction,
   597  		PortRangeMax:  &riEgress.PortRangeMax,
   598  		PortRangeMin:  &riEgress.PortRangeMin,
   599  		IPProtocol:    &riEgress.IPProtocol,
   600  	}
   601  	rule6 := neutron.SecurityGroupRuleV2{
   602  		Id:             "5",
   603  		ParentGroupId:  group1.Id,
   604  		Direction:      riIngress6.Direction,
   605  		PortRangeMax:   &riIngress6.PortRangeMax,
   606  		PortRangeMin:   &riIngress6.PortRangeMin,
   607  		IPProtocol:     &riIngress6.IPProtocol,
   608  		RemoteIPPrefix: riIngress6.RemoteIPPrefix,
   609  	}
   610  	ok := s.service.hasSecurityGroupRule(group1.Id, rule1.Id)
   611  	c.Assert(ok, gc.Equals, false)
   612  	ok = s.service.hasSecurityGroupRule(group2.Id, rule2.Id)
   613  	c.Assert(ok, gc.Equals, false)
   614  	var req struct {
   615  		Rule neutron.RuleInfoV2 `json:"security_group_rule"`
   616  	}
   617  	req.Rule = riIngress
   618  	var expected struct {
   619  		Rule neutron.SecurityGroupRuleV2 `json:"security_group_rule"`
   620  	}
   621  	resp, err := s.jsonRequest("POST", neutron.ApiSecurityGroupRulesV2, req, nil)
   622  	c.Assert(err, gc.IsNil)
   623  	c.Assert(resp.StatusCode, gc.Equals, http.StatusCreated)
   624  	assertJSON(c, resp, &expected)
   625  	c.Assert(expected.Rule.Id, gc.Equals, rule1.Id)
   626  	c.Assert(expected.Rule.ParentGroupId, gc.Equals, rule1.ParentGroupId)
   627  	c.Assert(*expected.Rule.PortRangeMax, gc.Equals, *rule1.PortRangeMax)
   628  	c.Assert(*expected.Rule.PortRangeMin, gc.Equals, *rule1.PortRangeMin)
   629  	c.Assert(*expected.Rule.IPProtocol, gc.Equals, *rule1.IPProtocol)
   630  	c.Assert(expected.Rule.Direction, gc.Equals, rule1.Direction)
   631  	c.Assert(expected.Rule.RemoteIPPrefix, gc.Equals, rule1.RemoteIPPrefix)
   632  	// Attempt to create duplicate rule should fail
   633  	resp, err = s.jsonRequest("POST", neutron.ApiSecurityGroupRulesV2, req, nil)
   634  	c.Assert(resp.StatusCode, gc.Equals, http.StatusBadRequest)
   635  	err = s.service.removeSecurityGroupRule(rule1.Id)
   636  	c.Assert(err, gc.IsNil)
   637  	// Attempt to create rule with all fields but RemoteIPPrefix identical should pass
   638  	req.Rule = riIngress2
   639  	resp, err = s.jsonRequest("POST", neutron.ApiSecurityGroupRulesV2, req, nil)
   640  	c.Assert(err, gc.IsNil)
   641  	c.Assert(resp.StatusCode, gc.Equals, http.StatusCreated)
   642  	err = s.service.removeSecurityGroupRule(rule2.Id)
   643  	c.Assert(err, gc.IsNil)
   644  	assertJSON(c, resp, &expected)
   645  	c.Assert(expected.Rule.Id, gc.Equals, rule2.Id)
   646  	c.Assert(expected.Rule.ParentGroupId, gc.Equals, rule2.ParentGroupId)
   647  	c.Assert(*expected.Rule.PortRangeMax, gc.Equals, *rule2.PortRangeMax)
   648  	c.Assert(*expected.Rule.PortRangeMin, gc.Equals, *rule2.PortRangeMin)
   649  	c.Assert(*expected.Rule.IPProtocol, gc.Equals, *rule2.IPProtocol)
   650  	c.Assert(expected.Rule.Direction, gc.Equals, rule2.Direction)
   651  	c.Assert(expected.Rule.RemoteIPPrefix, gc.Equals, rule2.RemoteIPPrefix)
   652  	req.Rule = riEgress
   653  	resp, err = s.jsonRequest("POST", neutron.ApiSecurityGroupRulesV2, req, nil)
   654  	c.Assert(err, gc.IsNil)
   655  	c.Assert(resp.StatusCode, gc.Equals, http.StatusCreated)
   656  	err = s.service.removeSecurityGroupRule(rule3.Id)
   657  	c.Assert(err, gc.IsNil)
   658  	assertJSON(c, resp, &expected)
   659  	c.Assert(expected.Rule.Id, gc.Equals, rule3.Id)
   660  	c.Assert(expected.Rule.ParentGroupId, gc.Equals, rule3.ParentGroupId)
   661  	// Attempt to create rule with IPv6 RemoteIPPrefix without specifying EthernetType, should fail
   662  	req.Rule = riIngress6
   663  	req.Rule.EthernetType = ""
   664  	resp, err = s.jsonRequest("POST", neutron.ApiSecurityGroupRulesV2, req, nil)
   665  	c.Assert(resp.StatusCode, gc.Equals, http.StatusBadRequest)
   666  	// Attempt to create rule with IPv6 RemoteIPPrefix with correct EthernetType, should pass
   667  	req.Rule = riIngress6
   668  	resp, err = s.jsonRequest("POST", neutron.ApiSecurityGroupRulesV2, req, nil)
   669  	c.Assert(err, gc.IsNil)
   670  	c.Assert(resp.StatusCode, gc.Equals, http.StatusCreated)
   671  	err = s.service.removeSecurityGroupRule(rule6.Id)
   672  	c.Assert(err, gc.IsNil)
   673  	assertJSON(c, resp, &expected)
   674  	c.Assert(expected.Rule.Id, gc.Equals, rule6.Id)
   675  	c.Assert(expected.Rule.ParentGroupId, gc.Equals, rule6.ParentGroupId)
   676  }
   677  
   678  func (s *NeutronHTTPSuite) TestDeleteSecurityGroupRule(c *gc.C) {
   679  	group1 := neutron.SecurityGroupV2{Id: "1", Name: "src", TenantId: s.service.TenantId}
   680  	group2 := neutron.SecurityGroupV2{Id: "2", Name: "tgt", TenantId: s.service.TenantId}
   681  	err := s.service.addSecurityGroup(group1)
   682  	c.Assert(err, gc.IsNil)
   683  	defer s.service.removeSecurityGroup(group1.Id)
   684  	err = s.service.addSecurityGroup(group2)
   685  	c.Assert(err, gc.IsNil)
   686  	defer s.service.removeSecurityGroup(group2.Id)
   687  	riGroup := neutron.RuleInfoV2{
   688  		ParentGroupId: group2.Id,
   689  		Direction:     "egress",
   690  	}
   691  	rule := neutron.SecurityGroupRuleV2{
   692  		Id:            "1",
   693  		ParentGroupId: group2.Id,
   694  		Direction:     "egress",
   695  	}
   696  	err = s.service.addSecurityGroupRule(rule.Id, riGroup)
   697  	c.Assert(err, gc.IsNil)
   698  	url := fmt.Sprintf("%s/%s", neutron.ApiSecurityGroupRulesV2, "1")
   699  	resp, err := s.authRequest("DELETE", url, nil, nil)
   700  	c.Assert(err, gc.IsNil)
   701  	c.Assert(resp.StatusCode, gc.Equals, http.StatusNoContent)
   702  	ok := s.service.hasSecurityGroupRule(group2.Id, rule.Id)
   703  	c.Assert(ok, gc.Equals, false)
   704  }
   705  
   706  func (s *NeutronHTTPSuite) TestPostFloatingIPV2(c *gc.C) {
   707  	// network 998 has External = true
   708  	fip := neutron.FloatingIPV2{Id: "1", IP: "10.0.0.1", FloatingNetworkId: "998"}
   709  	c.Assert(s.service.allFloatingIPs(nil), gc.HasLen, 0)
   710  	var req struct {
   711  		IP neutron.FloatingIPV2 `json:"floatingip"`
   712  	}
   713  	req.IP = fip
   714  	resp, err := s.jsonRequest("POST", neutron.ApiFloatingIPsV2, req, nil)
   715  	c.Assert(err, gc.IsNil)
   716  	c.Assert(resp.StatusCode, gc.Equals, http.StatusCreated)
   717  	var expected struct {
   718  		IP neutron.FloatingIPV2 `json:"floatingip"`
   719  	}
   720  	assertJSON(c, resp, &expected)
   721  	c.Assert(expected.IP, gc.DeepEquals, fip)
   722  	err = s.service.removeFloatingIP(fip.Id)
   723  	c.Assert(err, gc.IsNil)
   724  	// network 999 has External = false
   725  	req.IP.FloatingNetworkId = "999"
   726  	resp, err = s.jsonRequest("POST", neutron.ApiFloatingIPsV2, req, nil)
   727  	c.Assert(resp.StatusCode, gc.Equals, http.StatusNotFound)
   728  }
   729  
   730  func (s *NeutronHTTPSuite) TestGetFloatingIPs(c *gc.C) {
   731  	c.Assert(s.service.allFloatingIPs(nil), gc.HasLen, 0)
   732  	var expected struct {
   733  		IPs []neutron.FloatingIPV2 `json:"floatingips"`
   734  	}
   735  	resp, err := s.authRequest("GET", neutron.ApiFloatingIPsV2, nil, nil)
   736  	c.Assert(err, gc.IsNil)
   737  	c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)
   738  	assertJSON(c, resp, &expected)
   739  	c.Assert(expected.IPs, gc.HasLen, 0)
   740  	fips := []neutron.FloatingIPV2{
   741  		{Id: "1", IP: "1.2.3.4"},
   742  		{Id: "2", IP: "4.3.2.1"},
   743  	}
   744  	for _, fip := range fips {
   745  		err := s.service.addFloatingIP(fip)
   746  		defer s.service.removeFloatingIP(fip.Id)
   747  		c.Assert(err, gc.IsNil)
   748  	}
   749  	resp, err = s.authRequest("GET", neutron.ApiFloatingIPsV2, nil, nil)
   750  	c.Assert(err, gc.IsNil)
   751  	c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)
   752  	assertJSON(c, resp, &expected)
   753  	if expected.IPs[0].Id != fips[0].Id {
   754  		expected.IPs[0], expected.IPs[1] = expected.IPs[1], expected.IPs[0]
   755  	}
   756  	c.Assert(expected.IPs, gc.DeepEquals, fips)
   757  	var expectedIP struct {
   758  		IP neutron.FloatingIPV2 `json:"floatingip"`
   759  	}
   760  	url := fmt.Sprintf("%s/%s", neutron.ApiFloatingIPsV2, "1")
   761  	resp, err = s.authRequest("GET", url, nil, nil)
   762  	c.Assert(err, gc.IsNil)
   763  	c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)
   764  	assertJSON(c, resp, &expectedIP)
   765  	c.Assert(expectedIP.IP, gc.DeepEquals, fips[0])
   766  }
   767  
   768  func (s *NeutronHTTPSuite) TestDeleteFloatingIP(c *gc.C) {
   769  	fip := neutron.FloatingIPV2{Id: "1", IP: "10.0.0.1"}
   770  	err := s.service.addFloatingIP(fip)
   771  	c.Assert(err, gc.IsNil)
   772  	defer s.service.removeFloatingIP(fip.Id)
   773  	url := fmt.Sprintf("%s/%s", neutron.ApiFloatingIPsV2, "1")
   774  	resp, err := s.authRequest("DELETE", url, nil, nil)
   775  	c.Assert(err, gc.IsNil)
   776  	c.Assert(resp.StatusCode, gc.Equals, http.StatusNoContent)
   777  	_, err = s.service.floatingIP(fip.Id)
   778  	c.Assert(err, gc.NotNil)
   779  }
   780  
   781  func (s *NeutronHTTPSuite) TestGetNetworks(c *gc.C) {
   782  	// There are always 4 networks
   783  	networks, err := s.service.allNetworks(nil)
   784  	c.Assert(err, gc.IsNil)
   785  	c.Assert(networks, gc.HasLen, 5)
   786  	var expected struct {
   787  		Networks []neutron.NetworkV2 `json:"networks"`
   788  	}
   789  	resp, err := s.authRequest("GET", neutron.ApiNetworksV2, nil, nil)
   790  	c.Assert(err, gc.IsNil)
   791  	c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)
   792  	assertJSON(c, resp, &expected)
   793  	c.Assert(expected.Networks, gc.HasLen, len(networks))
   794  	var expectedNetwork struct {
   795  		Network neutron.NetworkV2 `json:"network"`
   796  	}
   797  	url := fmt.Sprintf("%s/%s", neutron.ApiNetworksV2, networks[0].Id)
   798  	resp, err = s.authRequest("GET", url, nil, nil)
   799  	c.Assert(err, gc.IsNil)
   800  	c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)
   801  	assertJSON(c, resp, &expectedNetwork)
   802  	c.Assert(expectedNetwork.Network, gc.DeepEquals, networks[0])
   803  }
   804  
   805  func (s *NeutronHTTPSuite) TestGetSubnets(c *gc.C) {
   806  	// There are always 3 subnets
   807  	subnets := s.service.allSubnets()
   808  	c.Assert(subnets, gc.HasLen, 3)
   809  	var expected struct {
   810  		Subnets []neutron.SubnetV2 `json:"subnets"`
   811  	}
   812  	resp, err := s.authRequest("GET", neutron.ApiSubnetsV2, nil, nil)
   813  	c.Assert(err, gc.IsNil)
   814  	c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)
   815  	assertJSON(c, resp, &expected)
   816  	c.Assert(expected.Subnets, gc.HasLen, 3)
   817  	var expectedSubnet struct {
   818  		Subnet neutron.SubnetV2 `json:"subnet"`
   819  	}
   820  	url := fmt.Sprintf("%s/%s", neutron.ApiSubnetsV2, subnets[0].Id)
   821  	resp, err = s.authRequest("GET", url, nil, nil)
   822  	c.Assert(err, gc.IsNil)
   823  	c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)
   824  	assertJSON(c, resp, &expectedSubnet)
   825  	c.Assert(expectedSubnet.Subnet, gc.DeepEquals, subnets[0])
   826  }
   827  
   828  func (s *NeutronHTTPSuite) TestGetPorts(c *gc.C) {
   829  	// There is always a default port.
   830  	ports := s.service.allPorts()
   831  	c.Assert(ports, gc.HasLen, 0)
   832  	var expected struct {
   833  		Ports []neutron.PortV2 `json:"ports"`
   834  	}
   835  	resp, err := s.authRequest("GET", neutron.ApiPortsV2, nil, nil)
   836  	c.Assert(err, gc.IsNil)
   837  	c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)
   838  	assertJSON(c, resp, &expected)
   839  	c.Assert(expected.Ports, gc.HasLen, 0)
   840  
   841  	ports = []neutron.PortV2{
   842  		{
   843  			Id:        "1",
   844  			Name:      "group 1",
   845  			TenantId:  s.service.TenantId,
   846  			NetworkId: "a87cc70a-3e15-4acf-8205-9b711a3531b7",
   847  			Tags:      []string{"tag0", "tag1"},
   848  		},
   849  		{
   850  			Id:        "2",
   851  			Name:      "group 2",
   852  			TenantId:  s.service.TenantId,
   853  			NetworkId: "a87cc70a-3e15-4acf-8205-9b711a3531xx",
   854  			Tags:      []string{"tag3", "tag2"},
   855  		},
   856  	}
   857  
   858  	for _, group := range ports {
   859  		err := s.service.addPort(group)
   860  		c.Assert(err, gc.IsNil)
   861  		defer s.service.removePort(group.Id)
   862  	}
   863  
   864  	resp, err = s.authRequest("GET", neutron.ApiPortsV2, nil, nil)
   865  	c.Assert(err, gc.IsNil)
   866  	c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)
   867  	assertJSON(c, resp, &expected)
   868  	c.Assert(expected.Ports, gc.HasLen, len(ports))
   869  
   870  	checkPortsInList(c, ports, expected.Ports)
   871  
   872  	var expectedPort struct {
   873  		Port neutron.PortV2 `json:"port"`
   874  	}
   875  	url := fmt.Sprintf("%s/%s", neutron.ApiPortsV2, "1")
   876  	resp, err = s.authRequest("GET", url, nil, nil)
   877  	c.Assert(err, gc.IsNil)
   878  	c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)
   879  	assertJSON(c, resp, &expectedPort)
   880  	c.Assert(expectedPort.Port, gc.DeepEquals, ports[0])
   881  }
   882  
   883  func (s *NeutronHTTPSuite) TestAddPort(c *gc.C) {
   884  	port := neutron.PortV2{
   885  		Id:          "1",
   886  		Name:        "port 1",
   887  		Description: "desc",
   888  		TenantId:    s.service.TenantId,
   889  		NetworkId:   "a87cc70a-3e15-4acf-8205-9b711a3531b7",
   890  		Tags:        []string{"tag0", "tag1"},
   891  	}
   892  	_, err := s.service.port(port.Id)
   893  	c.Assert(err, gc.NotNil)
   894  
   895  	var req struct {
   896  		Port struct {
   897  			Name        string   `json:"name"`
   898  			Description string   `json:"description"`
   899  			NetworkId   string   `json:"network_id"`
   900  			Tags        []string `json:"tags"`
   901  		} `json:"port"`
   902  	}
   903  	req.Port.Name = port.Name
   904  	req.Port.Description = port.Description
   905  	req.Port.NetworkId = port.NetworkId
   906  	req.Port.Tags = port.Tags
   907  
   908  	var expected struct {
   909  		Port neutron.PortV2 `json:"port"`
   910  	}
   911  	resp, err := s.jsonRequest("POST", neutron.ApiPortsV2, req, nil)
   912  	c.Assert(err, gc.IsNil)
   913  	c.Assert(resp.StatusCode, gc.Equals, http.StatusCreated)
   914  	assertJSON(c, resp, &expected)
   915  	c.Assert(expected.Port, gc.DeepEquals, port)
   916  
   917  	err = s.service.removePort(port.Id)
   918  	c.Assert(err, gc.IsNil)
   919  }
   920  
   921  func (s *NeutronHTTPSuite) TestDeletePort(c *gc.C) {
   922  	port := neutron.PortV2{Id: "1", Name: "port 1", TenantId: s.service.TenantId}
   923  	_, err := s.service.port(port.Id)
   924  	c.Assert(err, gc.NotNil)
   925  
   926  	err = s.service.addPort(port)
   927  	c.Assert(err, gc.IsNil)
   928  	defer s.service.removePort(port.Id)
   929  
   930  	url := fmt.Sprintf("%s/%s", neutron.ApiPortsV2, "1")
   931  	resp, err := s.authRequest("DELETE", url, nil, nil)
   932  	c.Assert(err, gc.IsNil)
   933  	c.Assert(resp.StatusCode, gc.Equals, http.StatusNoContent)
   934  
   935  	_, err = s.service.port(port.Id)
   936  	c.Assert(err, gc.NotNil)
   937  }
   938  
   939  func (s *NeutronHTTPSSuite) SetUpSuite(c *gc.C) {
   940  	s.HTTPSuite.SetUpSuite(c)
   941  	identityDouble := identityservice.NewUserPass()
   942  	userInfo := identityDouble.AddUser("fred", "secret", "tenant", "default")
   943  	s.token = userInfo.Token
   944  	c.Assert(s.Server.URL[:8], gc.Equals, "https://")
   945  	s.service = New(s.Server.URL, versionPath, userInfo.TenantId, region, identityDouble, nil)
   946  	s.service.AddNeutronModel(neutronmodel.New())
   947  }
   948  
   949  func (s *NeutronHTTPSSuite) TearDownSuite(c *gc.C) {
   950  	s.HTTPSuite.TearDownSuite(c)
   951  }
   952  
   953  func (s *NeutronHTTPSSuite) SetUpTest(c *gc.C) {
   954  	s.HTTPSuite.SetUpTest(c)
   955  	s.service.SetupHTTP(s.Mux)
   956  }
   957  
   958  func (s *NeutronHTTPSSuite) TearDownTest(c *gc.C) {
   959  	s.HTTPSuite.TearDownTest(c)
   960  }
   961  
   962  func (s *NeutronHTTPSSuite) TestHasHTTPSServiceURL(c *gc.C) {
   963  	endpoints := s.service.Endpoints()
   964  	c.Assert(endpoints[0].PublicURL[:8], gc.Equals, "https://")
   965  }