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

     1  // Neutron double testing service - HTTP API implementation
     2  
     3  package neutronservice
     4  
     5  import (
     6  	"crypto/rand"
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"net/http"
    12  	"path"
    13  	"strconv"
    14  	"strings"
    15  
    16  	"gopkg.in/goose.v2/neutron"
    17  	"gopkg.in/goose.v2/testservices"
    18  	"gopkg.in/goose.v2/testservices/identityservice"
    19  )
    20  
    21  const (
    22  	authToken               = "X-Auth-Token"
    23  	apiFloatingIPsV2        = "/v2.0/" + neutron.ApiFloatingIPsV2
    24  	apiNetworksV2           = "/v2.0/" + neutron.ApiNetworksV2
    25  	apiPortsV2              = "/v2.0/" + neutron.ApiPortsV2
    26  	apiSubnetsV2            = "/v2.0/" + neutron.ApiSubnetsV2
    27  	apiSecurityGroupsV2     = "/v2.0/" + neutron.ApiSecurityGroupsV2
    28  	apiSecurityGroupRulesV2 = "/v2.0/" + neutron.ApiSecurityGroupRulesV2
    29  )
    30  
    31  // errorResponse defines a single HTTP error response.
    32  type errorResponse struct {
    33  	code        int
    34  	body        string
    35  	contentType string
    36  	errorText   string
    37  	headers     map[string]string
    38  	neutron     *Neutron
    39  }
    40  
    41  // verbatim real Neutron responses (as errors).
    42  var (
    43  	errUnauthorized = &errorResponse{
    44  		http.StatusUnauthorized,
    45  		`401 Unauthorized
    46  
    47  This server could not verify that you are authorized to access the ` +
    48  			`document you requested. Either you supplied the wrong ` +
    49  			`credentials (e.g., bad password), or your browser does ` +
    50  			`not understand how to supply the credentials required.
    51  
    52   Authentication required
    53  `,
    54  		"text/plain; charset=UTF-8",
    55  		"unauthorized request",
    56  		nil,
    57  		nil,
    58  	}
    59  	errForbidden = &errorResponse{
    60  		http.StatusForbidden,
    61  		`{"forbidden": {"message": "Policy doesn't allow compute_extension:` +
    62  			`flavormanage to be performed.", "code": 403}}`,
    63  		"application/json; charset=UTF-8",
    64  		"forbidden flavors request",
    65  		nil,
    66  		nil,
    67  	}
    68  	errBadRequestMalformedURL = &errorResponse{
    69  		http.StatusBadRequest,
    70  		`{"badRequest": {"message": "Malformed request url", "code": 400}}`,
    71  		"application/json; charset=UTF-8",
    72  		"bad request base path or URL",
    73  		nil,
    74  		nil,
    75  	}
    76  	errBadRequestIncorrect = &errorResponse{
    77  		http.StatusBadRequest,
    78  		`{"badRequest": {"message": "The server could not comply with the ` +
    79  			`request since it is either malformed or otherwise incorrect.", "code": 400}}`,
    80  		"application/json; charset=UTF-8",
    81  		"bad request URL",
    82  		nil,
    83  		nil,
    84  	}
    85  	errNotFound = &errorResponse{
    86  		http.StatusNotFound,
    87  		`404 Not Found
    88  
    89  The resource could not be found.
    90  
    91  
    92  `,
    93  		"text/plain; charset=UTF-8",
    94  		"resource not found",
    95  		nil,
    96  		nil,
    97  	}
    98  	errNotFoundJSON = &errorResponse{
    99  		http.StatusNotFound,
   100  		`{"itemNotFound": {"message": "The resource could not be found.", "code": 404}}`,
   101  		"application/json; charset=UTF-8",
   102  		"resource not found",
   103  		nil,
   104  		nil,
   105  	}
   106  	errNotFoundJSONSG = &errorResponse{
   107  		http.StatusNotFound,
   108  		`{"itemNotFound": {"message": "Security group $ID$ not found.", "code": 404}}`,
   109  		"application/json; charset=UTF-8",
   110  		"",
   111  		nil,
   112  		nil,
   113  	}
   114  	errNotFoundJSONSGR = &errorResponse{
   115  		http.StatusNotFound,
   116  		`{"itemNotFound": {"message": "Rule ($ID$) not found.", "code": 404}}`,
   117  		"application/json; charset=UTF-8",
   118  		"security rule not found",
   119  		nil,
   120  		nil,
   121  	}
   122  	errNotFoundJSONP = &errorResponse{
   123  		http.StatusNotFound,
   124  		`{"itemNotFound": {"message": "Port $ID$ not found.", "code": 404}}`,
   125  		"application/json; charset=UTF-8",
   126  		"",
   127  		nil,
   128  		nil,
   129  	}
   130  	errMultipleChoices = &errorResponse{
   131  		http.StatusMultipleChoices,
   132  		`{"choices": [{"status": "CURRENT", "media-types": [{"base": ` +
   133  			`"application/xml", "type": "application/vnd.openstack.compute+` +
   134  			`xml;version=2"}, {"base": "application/json", "type": "application/` +
   135  			`vnd.openstack.compute+json;version=2"}], "id": "v2.0", "links": ` +
   136  			`[{"href": "$ENDPOINT$$URL$", "rel": "self"}]}]}`,
   137  		"application/json",
   138  		"multiple URL redirection choices",
   139  		nil,
   140  		nil,
   141  	}
   142  	errNoVersion = &errorResponse{
   143  		http.StatusOK,
   144  		`{"versions": [{"status": "CURRENT", "id": "v2.0", "links": [{"href": "v2.0", "rel": "self"}]}]}`,
   145  		"application/json",
   146  		"no version specified in URL",
   147  		nil,
   148  		nil,
   149  	}
   150  	errNotImplemented = &errorResponse{
   151  		http.StatusNotImplemented,
   152  		"501 Not Implemented",
   153  		"text/plain; charset=UTF-8",
   154  		"not implemented",
   155  		nil,
   156  		nil,
   157  	}
   158  	errNoGroupId = &errorResponse{
   159  		errorText: "no security group id given",
   160  	}
   161  	errNoPortId = &errorResponse{
   162  		errorText: "no port id given",
   163  	}
   164  	errRateLimitExceeded = &errorResponse{
   165  		http.StatusRequestEntityTooLarge,
   166  		"",
   167  		"text/plain; charset=UTF-8",
   168  		"too many requests",
   169  		// RFC says that Retry-After should be an int, but we don't want to wait an entire second during the test suite.
   170  		map[string]string{"Retry-After": "0.001"},
   171  		nil,
   172  	}
   173  	errNoMoreFloatingIPs = &errorResponse{
   174  		http.StatusNotFound,
   175  		"Zero floating ips available.",
   176  		"text/plain; charset=UTF-8",
   177  		"zero floating ips available",
   178  		nil,
   179  		nil,
   180  	}
   181  	errIPLimitExceeded = &errorResponse{
   182  		http.StatusRequestEntityTooLarge,
   183  		"Maximum number of floating ips exceeded.",
   184  		"text/plain; charset=UTF-8",
   185  		"maximum number of floating ips exceeded",
   186  		nil,
   187  		nil,
   188  	}
   189  )
   190  
   191  func (e *errorResponse) Error() string {
   192  	return e.errorText
   193  }
   194  
   195  // requestBody returns the body for the error response, replacing
   196  // $ENDPOINT$, $URL$, $ID$, and $ERROR$ in e.body with the values from
   197  // the request.
   198  func (e *errorResponse) requestBody(r *http.Request) []byte {
   199  	url := strings.TrimLeft(r.URL.Path, "/")
   200  	body := e.body
   201  	if body != "" {
   202  		if e.neutron != nil {
   203  			body = strings.Replace(body, "$ENDPOINT$", e.neutron.endpointURL(true, "/"), -1)
   204  		}
   205  		body = strings.Replace(body, "$URL$", url, -1)
   206  		body = strings.Replace(body, "$ERROR$", e.Error(), -1)
   207  		if slash := strings.LastIndex(url, "/"); slash != -1 {
   208  			body = strings.Replace(body, "$ID$", url[slash+1:], -1)
   209  		}
   210  	}
   211  	return []byte(body)
   212  }
   213  
   214  func (e *errorResponse) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   215  	if e.contentType != "" {
   216  		w.Header().Set("Content-Type", e.contentType)
   217  	}
   218  	body := e.requestBody(r)
   219  	if e.headers != nil {
   220  		for h, v := range e.headers {
   221  			w.Header().Set(h, v)
   222  		}
   223  	}
   224  	// workaround for https://code.google.com/p/go/issues/detail?id=4454
   225  	w.Header().Set("Content-Length", strconv.Itoa(len(body)))
   226  	if e.code != 0 {
   227  		w.WriteHeader(e.code)
   228  	}
   229  	if len(body) > 0 {
   230  		w.Write(body)
   231  	}
   232  }
   233  
   234  type neutronHandler struct {
   235  	n      *Neutron
   236  	method func(n *Neutron, w http.ResponseWriter, r *http.Request) error
   237  }
   238  
   239  func userInfo(i identityservice.IdentityService, r *http.Request) (*identityservice.UserInfo, error) {
   240  	return i.FindUser(r.Header.Get(authToken))
   241  }
   242  
   243  func (h *neutronHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   244  	path := r.URL.Path
   245  	// handle invalid X-Auth-Token header
   246  	_, err := userInfo(h.n.IdentityService, r)
   247  	if err != nil {
   248  		errUnauthorized.ServeHTTP(w, r)
   249  		return
   250  	}
   251  	// handle trailing slash in the path
   252  	if strings.HasSuffix(path, "/") && path != "/" {
   253  		errNotFound.ServeHTTP(w, r)
   254  		return
   255  	}
   256  	err = h.method(h.n, w, r)
   257  	if err == nil {
   258  		return
   259  	}
   260  	var resp http.Handler
   261  
   262  	if err == testservices.RateLimitExceededError {
   263  		resp = errRateLimitExceeded
   264  	} else if err == testservices.NoMoreFloatingIPs {
   265  		resp = errNoMoreFloatingIPs
   266  	} else if err == testservices.IPLimitExceeded {
   267  		resp = errIPLimitExceeded
   268  	} else {
   269  		resp, _ = err.(http.Handler)
   270  		if resp == nil {
   271  			code, encodedErr := errorJSONEncode(err)
   272  			resp = &errorResponse{
   273  				code,
   274  				encodedErr,
   275  				"application/json",
   276  				err.Error(),
   277  				nil,
   278  				h.n,
   279  			}
   280  		}
   281  	}
   282  	resp.ServeHTTP(w, r)
   283  }
   284  
   285  func writeResponse(w http.ResponseWriter, code int, body []byte) {
   286  	// workaround for https://code.google.com/p/go/issues/detail?id=4454
   287  	w.Header().Set("Content-Length", strconv.Itoa(len(body)))
   288  	w.WriteHeader(code)
   289  	w.Write(body)
   290  }
   291  
   292  // sendJSON sends the specified response serialized as JSON.
   293  func sendJSON(code int, resp interface{}, w http.ResponseWriter, r *http.Request) error {
   294  	data, err := json.Marshal(resp)
   295  	if err != nil {
   296  		return err
   297  	}
   298  	writeResponse(w, code, data)
   299  	return nil
   300  }
   301  
   302  func (n *Neutron) handler(method func(n *Neutron, w http.ResponseWriter, r *http.Request) error) http.Handler {
   303  	return &neutronHandler{n, method}
   304  }
   305  
   306  func (n *Neutron) handleRoot(w http.ResponseWriter, r *http.Request) error {
   307  	if r.URL.Path == "/" {
   308  		return errNoVersion
   309  	}
   310  	return errMultipleChoices
   311  }
   312  
   313  func (n *Neutron) HandleRoot(w http.ResponseWriter, r *http.Request) {
   314  	n.handler((*Neutron).handleRoot).ServeHTTP(w, r)
   315  }
   316  
   317  // newUUID generates a random UUID conforming to RFC 4122.
   318  func newUUID() (string, error) {
   319  	uuid := make([]byte, 16)
   320  	if _, err := io.ReadFull(rand.Reader, uuid); err != nil {
   321  		return "", err
   322  	}
   323  	uuid[8] = uuid[8]&^0xc0 | 0x80 // variant bits; see section 4.1.1.
   324  	uuid[6] = uuid[6]&^0xf0 | 0x40 // version 4; see section 4.1.3.
   325  	return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]), nil
   326  }
   327  
   328  // processGroupId returns the group if one is specified in the given request,
   329  // either by id or by ?name. If there was no group id specified in the path,
   330  // it returns errNoGroupId
   331  func (n *Neutron) processGroupId(w http.ResponseWriter, r *http.Request) (*neutron.SecurityGroupV2, error) {
   332  	groupId := path.Base(r.URL.Path)
   333  	apiFunc := path.Base(apiSecurityGroupsV2)
   334  	if groupId != apiFunc {
   335  		group, err := n.securityGroup(groupId)
   336  		if err != nil {
   337  			return nil, errNotFoundJSONSG
   338  		}
   339  		return group, nil
   340  	}
   341  	return nil, errNoGroupId
   342  }
   343  
   344  // handleSecurityGroups handles the /v2.0/security-groups HTTP API.
   345  func (n *Neutron) handleSecurityGroups(w http.ResponseWriter, r *http.Request) error {
   346  	switch r.Method {
   347  	case "GET":
   348  		group, err := n.processGroupId(w, r)
   349  		if err == errNoGroupId {
   350  			groups := []neutron.SecurityGroupV2{}
   351  			query := r.URL.Query()
   352  			if len(query) == 1 {
   353  				secGroupName := query["name"][0]
   354  				groups, err = n.securityGroupByName(secGroupName)
   355  				if err != nil {
   356  					return err
   357  				}
   358  			} else {
   359  				groups = n.allSecurityGroups()
   360  			}
   361  			resp := struct {
   362  				Groups []neutron.SecurityGroupV2 `json:"security_groups"`
   363  			}{groups}
   364  			return sendJSON(http.StatusOK, resp, w, r)
   365  		}
   366  		if err != nil {
   367  			return err
   368  		}
   369  		resp := struct {
   370  			Group neutron.SecurityGroupV2 `json:"security_group"`
   371  		}{*group}
   372  
   373  		return sendJSON(http.StatusOK, resp, w, r)
   374  	case "POST":
   375  		_, err := n.processGroupId(w, r)
   376  		if err != errNoGroupId {
   377  			return errNotFound
   378  		}
   379  		body, err := ioutil.ReadAll(r.Body)
   380  		if err != nil || len(body) == 0 {
   381  			return errBadRequestIncorrect
   382  		}
   383  		var req struct {
   384  			Group struct {
   385  				Name        string
   386  				Description string
   387  			} `json:"security_group"`
   388  		}
   389  		if err := json.Unmarshal(body, &req); err != nil {
   390  			return err
   391  		} else {
   392  			n.nextGroupId++
   393  			nextId := strconv.Itoa(n.nextGroupId)
   394  			err = n.addSecurityGroup(neutron.SecurityGroupV2{
   395  				Id:          nextId,
   396  				Name:        req.Group.Name,
   397  				Description: req.Group.Description,
   398  				TenantId:    n.TenantId,
   399  			})
   400  			if err != nil {
   401  				return err
   402  			}
   403  			group, err := n.securityGroup(nextId)
   404  			if err != nil {
   405  				return err
   406  			}
   407  			var resp struct {
   408  				Group neutron.SecurityGroupV2 `json:"security_group"`
   409  			}
   410  			resp.Group = *group
   411  			return sendJSON(http.StatusCreated, resp, w, r)
   412  		}
   413  	case "PUT":
   414  		group, err := n.processGroupId(w, r)
   415  		if err == errNoGroupId {
   416  			return errNotFound
   417  		}
   418  
   419  		var req struct {
   420  			Group struct {
   421  				Name        string
   422  				Description string
   423  			} `json:"security_group"`
   424  		}
   425  		body, err := ioutil.ReadAll(r.Body)
   426  		if err != nil || len(body) == 0 {
   427  			return errBadRequestIncorrect
   428  		}
   429  		if err := json.Unmarshal(body, &req); err != nil {
   430  			return err
   431  		}
   432  
   433  		err = n.updateSecurityGroup(neutron.SecurityGroupV2{
   434  			Id:          group.Id,
   435  			Name:        req.Group.Name,
   436  			Description: req.Group.Description,
   437  			TenantId:    group.TenantId,
   438  		})
   439  		if err != nil {
   440  			return err
   441  		}
   442  		group, err = n.securityGroup(group.Id)
   443  		if err != nil {
   444  			return err
   445  		}
   446  		var resp struct {
   447  			Group neutron.SecurityGroupV2 `json:"security_group"`
   448  		}
   449  		resp.Group = *group
   450  		return sendJSON(http.StatusOK, resp, w, r)
   451  
   452  	case "DELETE":
   453  		if group, err := n.processGroupId(w, r); group != nil {
   454  			if err := n.removeSecurityGroup(group.Id); err != nil {
   455  				return err
   456  			}
   457  			writeResponse(w, http.StatusNoContent, nil)
   458  			return nil
   459  		} else if err == errNoGroupId {
   460  			return errNotFound
   461  		} else {
   462  			return err
   463  		}
   464  	}
   465  	return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path)
   466  }
   467  
   468  // handleSecurityGroupRules handles the /v2.0/security-group-rules HTTP API.
   469  func (n *Neutron) handleSecurityGroupRules(w http.ResponseWriter, r *http.Request) error {
   470  	switch r.Method {
   471  	case "GET":
   472  		return errNotFoundJSON
   473  	case "POST":
   474  		ruleId := path.Base(r.URL.Path)
   475  		apiFunc := path.Base(apiSecurityGroupRulesV2)
   476  		if ruleId != apiFunc {
   477  			return errNotFound
   478  		}
   479  		body, err := ioutil.ReadAll(r.Body)
   480  		if err != nil || len(body) == 0 {
   481  			return errBadRequestIncorrect
   482  		}
   483  		var req struct {
   484  			Rule neutron.RuleInfoV2 `json:"security_group_rule"`
   485  		}
   486  		if err = json.Unmarshal(body, &req); err != nil {
   487  			return err
   488  		}
   489  		inrule := req.Rule
   490  		// set default EthernetType for correct comparison
   491  		// TODO: we should probably have a neutronmodel func to parse and set default values
   492  		//       and/or move the duplicate check there
   493  		if inrule.EthernetType == "" {
   494  			inrule.EthernetType = "IPv4"
   495  		}
   496  		group, err := n.securityGroup(inrule.ParentGroupId)
   497  		if err != nil {
   498  			return err // TODO: should be a 4XX error with details
   499  		}
   500  		for _, r := range group.Rules {
   501  			// TODO: this logic is actually wrong, not what neutron does at all
   502  			// why are we reimplementing half of neutron/api/openstack in go again?
   503  			if r.IPProtocol != nil && *r.IPProtocol == inrule.IPProtocol &&
   504  				r.PortRangeMax != nil && *r.PortRangeMax == inrule.PortRangeMax &&
   505  				r.PortRangeMin != nil && *r.PortRangeMin == inrule.PortRangeMin &&
   506  				r.RemoteIPPrefix != "" && r.RemoteIPPrefix == inrule.RemoteIPPrefix &&
   507  				r.EthernetType != "" && r.EthernetType == inrule.EthernetType {
   508  				// TODO: Use a proper helper and sane error type
   509  				return &errorResponse{
   510  					http.StatusBadRequest,
   511  					fmt.Sprintf(`{"badRequest": {"message": "This rule already exists in group %s", "code": 400}}`, group.Id),
   512  					"application/json; charset=UTF-8",
   513  					"rule already exists",
   514  					nil,
   515  					nil,
   516  				}
   517  			}
   518  		}
   519  		n.nextRuleId++
   520  		nextId := strconv.Itoa(n.nextRuleId)
   521  		err = n.addSecurityGroupRule(nextId, req.Rule)
   522  		if err != nil {
   523  			return err
   524  		}
   525  		rule, err := n.securityGroupRule(nextId)
   526  		if err != nil {
   527  			return err
   528  		}
   529  		var resp struct {
   530  			Rule neutron.SecurityGroupRuleV2 `json:"security_group_rule"`
   531  		}
   532  		resp.Rule = *rule
   533  		return sendJSON(http.StatusCreated, resp, w, r)
   534  	case "PUT":
   535  		return errNotFound
   536  	case "DELETE":
   537  		ruleId := path.Base(r.URL.Path)
   538  		apiFunc := path.Base(apiSecurityGroupRulesV2)
   539  		if ruleId != apiFunc {
   540  			if _, err := n.securityGroupRule(ruleId); err != nil {
   541  				return errNotFoundJSONSGR
   542  			}
   543  			if err := n.removeSecurityGroupRule(ruleId); err != nil {
   544  				return err
   545  			}
   546  			writeResponse(w, http.StatusNoContent, nil)
   547  			return nil
   548  		}
   549  		return errNotFound
   550  	}
   551  	return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path)
   552  }
   553  
   554  // processPortId returns the group if one is specified in the given request,
   555  // either by id or by ?name. If there was no group id specified in the path,
   556  // it returns errNoPortId
   557  func (n *Neutron) processPortId(w http.ResponseWriter, r *http.Request) (*neutron.PortV2, error) {
   558  	portId := path.Base(r.URL.Path)
   559  	apiFunc := path.Base(apiPortsV2)
   560  	if portId != apiFunc {
   561  		port, err := n.port(portId)
   562  		if err != nil {
   563  			return nil, errNotFoundJSONP
   564  		}
   565  		return port, nil
   566  	}
   567  	return nil, errNoPortId
   568  }
   569  
   570  // handlePorts handles the /v2.0/ports HTTP API.
   571  func (n *Neutron) handlePorts(w http.ResponseWriter, r *http.Request) error {
   572  	switch r.Method {
   573  	case "GET":
   574  		port, err := n.processPortId(w, r)
   575  		if err == errNoPortId {
   576  			resp := struct {
   577  				Ports []neutron.PortV2 `json:"ports"`
   578  			}{n.allPorts()}
   579  
   580  			return sendJSON(http.StatusOK, resp, w, r)
   581  		}
   582  		if err != nil {
   583  			return err
   584  		}
   585  		resp := struct {
   586  			Port neutron.PortV2 `json:"port"`
   587  		}{*port}
   588  
   589  		return sendJSON(http.StatusOK, resp, w, r)
   590  	case "POST":
   591  		_, err := n.processPortId(w, r)
   592  		if err != errNoPortId {
   593  			return errNotFound
   594  		}
   595  		body, err := ioutil.ReadAll(r.Body)
   596  		if err != nil || len(body) == 0 {
   597  			return errBadRequestIncorrect
   598  		}
   599  		var req struct {
   600  			Port neutron.PortV2 `json:"port"`
   601  		}
   602  		if err := json.Unmarshal(body, &req); err != nil {
   603  			return err
   604  		} else {
   605  			n.nextPortId++
   606  			nextId := strconv.Itoa(n.nextPortId)
   607  			err = n.addPort(neutron.PortV2{
   608  				Id:          nextId,
   609  				Name:        req.Port.Name,
   610  				Description: req.Port.Description,
   611  				FixedIPs:    req.Port.FixedIPs,
   612  				NetworkId:   req.Port.NetworkId,
   613  				Status:      req.Port.Status,
   614  				Tags:        req.Port.Tags,
   615  				TenantId:    n.TenantId,
   616  			})
   617  			if err != nil {
   618  				return err
   619  			}
   620  			port, err := n.port(nextId)
   621  			if err != nil {
   622  				return err
   623  			}
   624  			var resp struct {
   625  				Port neutron.PortV2 `json:"port"`
   626  			}
   627  			resp.Port = *port
   628  			return sendJSON(http.StatusCreated, resp, w, r)
   629  		}
   630  
   631  	case "DELETE":
   632  		if port, err := n.processPortId(w, r); port != nil {
   633  			if err := n.removePort(port.Id); err != nil {
   634  				return err
   635  			}
   636  			writeResponse(w, http.StatusNoContent, nil)
   637  			return nil
   638  		} else if err == errNoPortId {
   639  			return errNotFound
   640  		} else {
   641  			return err
   642  		}
   643  	}
   644  	return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path)
   645  }
   646  
   647  // handleFloatingIPs handles the v2/floatingips HTTP API.
   648  func (n *Neutron) handleFloatingIPs(w http.ResponseWriter, r *http.Request) error {
   649  	switch r.Method {
   650  	case "GET":
   651  		ipId := path.Base(r.URL.Path)
   652  		apiFunc := path.Base(apiFloatingIPsV2)
   653  		if ipId != apiFunc {
   654  			fip, err := n.floatingIP(ipId)
   655  			if err != nil {
   656  				return errNotFoundJSON
   657  			}
   658  			resp := struct {
   659  				IP neutron.FloatingIPV2 `json:"floatingip"`
   660  			}{*fip}
   661  			return sendJSON(http.StatusOK, resp, w, r)
   662  		}
   663  		f := make(filter)
   664  		if err := r.ParseForm(); err == nil && len(r.Form) > 0 {
   665  			for filterKey, filterValues := range r.Form {
   666  				for _, value := range filterValues {
   667  					f[filterKey] = value
   668  				}
   669  			}
   670  		}
   671  		fips := n.allFloatingIPs(f)
   672  		if len(fips) == 0 {
   673  			fips = []neutron.FloatingIPV2{}
   674  		}
   675  		resp := struct {
   676  			IPs []neutron.FloatingIPV2 `json:"floatingips"`
   677  		}{fips}
   678  		return sendJSON(http.StatusOK, resp, w, r)
   679  	case "POST":
   680  		ipId := path.Base(r.URL.Path)
   681  		apiFunc := path.Base(apiFloatingIPsV2)
   682  		if ipId != apiFunc {
   683  			return errNotFound
   684  		}
   685  		body, err := ioutil.ReadAll(r.Body)
   686  		if err != nil || len(body) == 0 {
   687  			return errBadRequestIncorrect
   688  		}
   689  		var req struct {
   690  			FIP neutron.FloatingIPV2 `json:"floatingip"`
   691  		}
   692  		if err := json.Unmarshal(body, &req); err != nil {
   693  			return err
   694  		}
   695  		extNetwork, err := n.network(req.FIP.FloatingNetworkId)
   696  		if err != nil {
   697  			return err
   698  		}
   699  		if !extNetwork.External {
   700  			return errNotFound
   701  		}
   702  		n.nextIPId++
   703  		addr := fmt.Sprintf("10.0.0.%d", n.nextIPId)
   704  		nextId := strconv.Itoa(n.nextIPId)
   705  		fip := neutron.FloatingIPV2{FixedIP: "", Id: nextId, IP: addr, FloatingNetworkId: extNetwork.Id}
   706  		err = n.addFloatingIP(fip)
   707  		if err != nil {
   708  			return err
   709  		}
   710  		resp := struct {
   711  			FloatingIPV2 neutron.FloatingIPV2 `json:"floatingip"`
   712  		}{fip}
   713  		return sendJSON(http.StatusCreated, resp, w, r)
   714  	case "PUT":
   715  		return errNotFound
   716  	case "DELETE":
   717  		ipId := path.Base(r.URL.Path)
   718  		apiFunc := path.Base(apiFloatingIPsV2)
   719  		if ipId != apiFunc {
   720  			if err := n.removeFloatingIP(ipId); err == nil {
   721  				writeResponse(w, http.StatusNoContent, nil)
   722  				return nil
   723  			}
   724  			return errNotFoundJSON
   725  		}
   726  		return errNotFound
   727  	}
   728  	return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path)
   729  }
   730  
   731  // handleNetworks handles the v2/networks HTTP API.
   732  func (n *Neutron) handleNetworks(w http.ResponseWriter, r *http.Request) error {
   733  	switch r.Method {
   734  	case "GET":
   735  		networkId := path.Base(r.URL.Path)
   736  		apiFunc := path.Base(apiNetworksV2)
   737  		if networkId != apiFunc {
   738  			network, err := n.network(networkId)
   739  			if err != nil {
   740  				return errNotFoundJSON
   741  			}
   742  			resp := struct {
   743  				Network neutron.NetworkV2 `json:"network"`
   744  			}{*network}
   745  			return sendJSON(http.StatusOK, resp, w, r)
   746  		}
   747  		f := make(filter)
   748  		if err := r.ParseForm(); err == nil && len(r.Form) > 0 {
   749  			for filterKey, filterValues := range r.Form {
   750  				for _, value := range filterValues {
   751  					f[filterKey] = value
   752  				}
   753  			}
   754  		}
   755  		nets, err := n.allNetworks(f)
   756  		if err != nil {
   757  			return err
   758  		}
   759  		if len(nets) == 0 {
   760  			nets = []neutron.NetworkV2{}
   761  		}
   762  		resp := struct {
   763  			Network []neutron.NetworkV2 `json:"networks"`
   764  		}{nets}
   765  		return sendJSON(http.StatusOK, resp, w, r)
   766  	default:
   767  		return errNotFound
   768  	}
   769  	return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path)
   770  }
   771  
   772  // handleNetworks handles the v2/subnets HTTP API.
   773  func (n *Neutron) handleSubnets(w http.ResponseWriter, r *http.Request) error {
   774  	switch r.Method {
   775  	case "GET":
   776  		subnetId := path.Base(r.URL.Path)
   777  		apiFunc := path.Base(apiSubnetsV2)
   778  		if subnetId != apiFunc {
   779  			subnet, err := n.subnet(subnetId)
   780  			if err != nil {
   781  				return errNotFoundJSON
   782  			}
   783  			resp := struct {
   784  				Subnet neutron.SubnetV2 `json:"subnet"`
   785  			}{*subnet}
   786  			return sendJSON(http.StatusOK, resp, w, r)
   787  		}
   788  		subnets := n.allSubnets()
   789  		if len(subnets) == 0 {
   790  			subnets = []neutron.SubnetV2{}
   791  		}
   792  		resp := struct {
   793  			Subnets []neutron.SubnetV2 `json:"subnets"`
   794  		}{subnets}
   795  		return sendJSON(http.StatusOK, resp, w, r)
   796  	default:
   797  		return errNotFound
   798  	}
   799  	return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path)
   800  }
   801  
   802  // SetupHTTP attaches all the needed handlers to provide the HTTP API.
   803  func (n *Neutron) SetupHTTP(mux *http.ServeMux) {
   804  	handlers := map[string]http.Handler{
   805  		"/$v":                       errNoVersion,
   806  		"/$v/":                      errBadRequestMalformedURL,
   807  		"/$v/security-groups":       n.handler((*Neutron).handleSecurityGroups),
   808  		"/$v/security-groups/":      n.handler((*Neutron).handleSecurityGroups),
   809  		"/$v/security-group-rules":  n.handler((*Neutron).handleSecurityGroupRules),
   810  		"/$v/security-group-rules/": n.handler((*Neutron).handleSecurityGroupRules),
   811  		"/$v/ports":                 n.handler((*Neutron).handlePorts),
   812  		"/$v/ports/":                n.handler((*Neutron).handlePorts),
   813  		"/$v/floatingips":           n.handler((*Neutron).handleFloatingIPs),
   814  		"/$v/floatingips/":          n.handler((*Neutron).handleFloatingIPs),
   815  		"/$v/networks":              n.handler((*Neutron).handleNetworks),
   816  		"/$v/networks/":             n.handler((*Neutron).handleNetworks),
   817  		"/$v/subnets":               n.handler((*Neutron).handleSubnets),
   818  		"/$v/subnets/":              n.handler((*Neutron).handleSubnets),
   819  	}
   820  	for path, h := range handlers {
   821  		path = strings.Replace(path, "$v", n.VersionPath, 1)
   822  		mux.Handle(path, h)
   823  	}
   824  }
   825  
   826  func (n *Neutron) SetupRootHandler(mux *http.ServeMux) {
   827  	mux.Handle("/", n.handler((*Neutron).handleRoot))
   828  }