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 }