pax_global_header00006660000000000000000000000064141460540570014520gustar00rootroot0000000000000052 comment=19cddc231eddbec354f8a84d3838ece69f714ce3 golang-gopkg-goose.v1-0.0~git20170406.3228e4f/000077500000000000000000000000001414605405700201145ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/.gitignore000066400000000000000000000004741414605405700221110ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof ./tags testservices/testservices* TAGS *.sw[nop] golang-gopkg-goose.v1-0.0~git20170406.3228e4f/AUTHORS.md000066400000000000000000000027211414605405700215650ustar00rootroot00000000000000goamz Authors ============= This file contains a list of people who contributed relevant code to the project. Anyone with knowledge of OpenStack API who wants to get involved in the project maintenance, code reviews, issues triage, and helping goose users and community members in general, please consider contacting one of the project's [maintainers](https://github.com/orgs/go-goose/people) and request to become one. Check [CONTRIBUTING.md](CONTRIBUTING.md) for details. Please, keep the list sorted. Full name | E-mail | GitHub account ----------|--------|--------------- Andrew Wilkins | andrew.wilkins {at} canonical.com | [@axw](http://github.com/axw) Dave Cheney | dave.cheney {at} canonical.com | [@davecheney](http://github.com/davecheney) Dimiter Naydenov | dimiter.naydenov {at} canonical.com | [@dimitern](http://github.com/dimitern) Horacio DurĂ¡n | horacio.duran {at} canonical.com | [@perrito666](https://github.com/perrito666) Ian Booth | ian.booth {at} canonical.com | [@wallyworld](http://github.com/wallyworld) John Meinel | john.meinel {at} canonical.com | [@jameinel](https://github.com/jameinel) Jorge Niedbalski | jorge.niedbalski {at} canonical.com | [@niedbalski](https://github.com/niedbalski) Justin Santa Barbara | justin {at} meteor.com (?) | [@justinsb](https://github.com/justinsb) Kevin McDermott | kevin {at} bigkevmcd.com | [@bigkevmcd](https://github.com/bigkevmcd) Martin Packman | martin.packman {at} canonical.com | [@bz2](http://github.com/bz2) golang-gopkg-goose.v1-0.0~git20170406.3228e4f/CONTRIBUTING.md000066400000000000000000000125441414605405700223530ustar00rootroot00000000000000Contributing to goose ===================== We encourage everyone who is familiar with the [OpenStack API](http://developer.openstack.org/api-ref.html) and is willing to support and improve the project to become a contributor. Current list of official maintainers can be found on the [go-goose People](https://github.com/orgs/go-goose/people) list. Current and past contributors list is in the [AUTHORS.md](AUTHORS.md) file. This file contains instructions and guidelines for contributors. Code of conduct --------------- We are committed to providing a friendly, safe and welcoming environment for all users and community members. Please, review the [Ubuntu Code of Conduct](https://launchpad.net/codeofconduct/1.1), which covers the most important aspects we expect from contributors: * Be considerate - treat others nicely, everybody can provide valuable contributions. * Be respectful of others, even if you don't agree with them. Keep an open mind. * Be collaborative - this is essential in an open source community. * When we disagree, we consult others - it's important to solve disagreements constructively. * When unsure, ask for help. * Step down considerately - if you need to leave the project, minimize disruption. Ways to contribute ------------------ There are several ways to contribute to the project: * Report [issues](https://github.com/go-goose/goose/issues/new) you might have. * Please, make sure there is no existing [known issue](https://github.com/go-goose/goose/issues) when reporting a new one. * Propose a patch or a bug fix by opening a [pull request](https://help.github.com/articles/creating-a-pull-request/). Check GitHub help on [how to collaborate](https://help.github.com/categories/collaborating/). * Give feedback for [known issues](https://github.com/go-goose/goose/issues/) or proposed [pull requests](https://github.com/go-goose/goose/pulls). For some of those things you will need a [GitHub account](https://github.com/signup/free), if you don't have one. Contributing a patch -------------------- Found a bug or want to suggest an improvement? Great! Here are the steps anyone can follow to propose a bug fix or patch. * [Fork](https://help.github.com/articles/fork-a-repo/) the [`go-goose/goose`](https://github.com/go-goose/goose) repository. * If you think you found a bug, please check the existing [issues](https://github.com/go-goose/goose/issues) to see if it's a known problem. * Otherwise, [open a new issue](https://github.com/go-goose/goose/issues/new) for it. * Clone your forked repository locally: ``` $ git clone https://github.com//goose ``` * For the unit tests, you will need [gocheck.v1](https://github.com/go-check/check): ``` $ go get gopkg.in/check.v1 ``` * Create a feature branch for your contribution. Make your changes there. * It's recommended to try keeping your changes as small as possible. * Split bigger changes in several pull request to make the code review easier. * Be sure to write tests for your code changes and run them before proposing: ``` $ go test -check.v ``` * Push your feature branch to your fork. * Open a pull request with a description of your change. * A maintainer should notice your pull request and do a code review. You can also ask a [maintainer](https://github.com/orgs/go-goose/people) for review. * Reply to comments, fix issues, push your changes. * Depending on the size of the patch, this process can be repeated a few times. * All merges are gated on running the full test suite, so make sure to run all the tests locally. * Once you get an approval, ask a maintainer to merge your patch. Becoming an official maintainer ------------------------------- Thanks for considering becoming a maintainer of goose! It's not required to be a maintainer to contribute, but if you find yourself frequently proposing patches and can dedicate some of your time to help, please consider following the following procedure. * You need a [GitHub account](https://github.com/signup/free) if you don't have one. * Review and sign the Canonical [Contributor License Agreement](http://www.ubuntu.com/legal/contributors/). * For any questions about the CLA, you might find the [CLA FAQ](http://www.ubuntu.com/legal/contributors/licence-agreement-faq) page useful. * Request to become a maintainer by contacting the [existing maintainers](https://github.com/orgs/go-goose/people). * You're welcome to add your name to the [AUTHORS.md](AUTHORS.md) list once approved. General guidelines ------------------ The following list is not exhaustive or in any particular order. It providers things to keep in mind when contributing to goose. Be reasonable and considerate and please ask for help, if something is not clear. * Commit early, commit often. * Use `git rebase` before proposing your changes to squash minor commits. Let's keep the commit log cleaner. * **Do not** rebase commits you already pushed, even when in your own fork. Others might depend on them. * Write new tests and update existing ones when changing the code. All changes should have tests, when possible. * Use `go fmt` to format your code before pushing. * Document exported types, functions, constants, variables, etc. * See the excellent [Effective Go](http://golang.org/doc/effective_go.html) style guide, which we use. * When reporting issues, provide the necessary information to reproduce the issue. Thanks for your interest in goose! golang-gopkg-goose.v1-0.0~git20170406.3228e4f/LICENSE000066400000000000000000000167211414605405700211300ustar00rootroot00000000000000GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. golang-gopkg-goose.v1-0.0~git20170406.3228e4f/README.md000066400000000000000000000024221414605405700213730ustar00rootroot00000000000000goose ===== Go OpenStack Exchange (goose) - Go bindings for talking to OpenStack. [![GoDoc](https://godoc.org/gopkg.in/goose.v1?status.png)](http://godoc.org/gopkg.in/goose.v1) **NOTE**: This is a stable branch, which is compatible with the original goose source from [Launchpad](https://launchpad.net/goose/trunk), except for the changed import paths. Instructions ------------ Install the package with: go get gopkg.in/goose.v1/... Import it with: import "gopkg.in/goose.v1/" Example: import "gopkg.in/goose.v1/client" and use _client_ as the package name inside the code. The same applies to the other sub-packages: _nova_, _switft_, etc. For more details, check the API documentation: * https://gopkg.in/goose.v1 Contacts -------- You can contact directly one of the [maintainers](https://github.com/orgs/go-goose/people). Issues ------ Please report bugs by opening an [issue](https://github.com/go-goose/goose/issues). Contributing ------------ Contributors are most welcome! Please have a look at [CONTRIBUTING.md](CONTRIBUTING.md) for details. Authors ------- List of people who made relevant contributions to goose can be found in [AUTHORS.md](AUTHORS.md). License ------- goose is licensed under LGPLv3. See the [LICENSE](LICENSE) for details. golang-gopkg-goose.v1-0.0~git20170406.3228e4f/cinder/000077500000000000000000000000001414605405700213605ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/cinder/autogenerated_client.go000066400000000000000000001174461414605405700261110ustar00rootroot00000000000000// Copyright 2015 Canonical Ltd. // Licensed under the LGPLv3, see LICENCE file for details. // This file was initially autogenerated from the upstream wadl file, but has // since been updated by hand. // The WADLs were originally sourced from: // https://github.com/openstack/api-site/tree/master/api-ref/src/wadls/volume-api/src/v2 package cinder import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "net/url" ) // RequestHandlerFn specifies a function signature which wadl2go will // use to process the http.Request. What this means is up to the // implementor. type RequestHandlerFn func(*http.Request) (*http.Response, error) type UpdateVolumeTypeParams struct { // VolumeType is required. // // A volume type offers a way to categorize or group volumes. VolumeType string `json:"volume_type"` // // The unique identifier of the tenant or account. TenantId string `json:"-"` // VolumeTypeId is required. // // The unique identifier for an existing volume type. VolumeTypeId string `json:"-"` } type VolumeType struct { ExtraSpecs struct { Capabilities string `json:"capabilities"` } `json:"extra_specs"` ID string `json:"id"` Name string `json:"name"` } type UpdateVolumeTypeResults struct { VolumeType VolumeType `json:"volume_type"` } // // Updates a volume type. func updateVolumeType(client *Client, args UpdateVolumeTypeParams) (*UpdateVolumeTypeResults, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: fmt.Sprintf("types/%s", args.VolumeTypeId)} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("PUT", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("PUT", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 200: break } var results UpdateVolumeTypeResults json.Unmarshal(body, &results) return &results, nil } type UpdateVolumeTypeExtraSpecsParams struct { // VolumeType is required. // // A volume type offers a way to categorize or group volumes. VolumeType string `json:"volume_type"` // ExtraSpecs is required. // // A key:value pair that offers a way to show additional specifications associated with the volume type. Examples include capabilities, capacity, compression, and so on, depending on the storage driver in use. ExtraSpecs string `json:"extra_specs"` // // The unique identifier of the tenant or account. TenantId string `json:"-"` // VolumeTypeId is required. // // The unique identifier for an existing volume type. VolumeTypeId string `json:"-"` } type UpdateVolumeTypeExtraSpecsResults struct { VolumeType VolumeType `json:"volume_type"` } // // Updates the extra specifications assigned to a volume type. func updateVolumeTypeExtraSpecs(client *Client, args UpdateVolumeTypeExtraSpecsParams) (*UpdateVolumeTypeExtraSpecsResults, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: fmt.Sprintf("types/%s", args.VolumeTypeId)} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("PUT", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("PUT", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 200: break } var results UpdateVolumeTypeExtraSpecsResults json.Unmarshal(body, &results) return &results, nil } type GetSnapshotsSimpleParams struct { // // The unique identifier of the tenant or account. TenantId string `json:"-"` } type Snapshot struct { CreatedAt string `json:"created_at"` Description string `json:"description"` ID string `json:"id"` Metadata struct { Key string `json:"key"` } `json:"metadata"` Name string `json:"name"` Size int `json:"size"` Status string `json:"status"` VolumeID string `json:"volume_id"` Os_Extended_Snapshot_Attributes_Progress string `json:"os-extended-snapshot-attributes:progress"` Os_Extended_Snapshot_Attributes_ProjectID string `json:"os-extended-snapshot-attributes:project_id"` } type GetSnapshotsSimpleResults struct { Snapshots []Snapshot `json:"snapshots"` } // // Lists summary information for all Block Storage snapshots that the tenant who submits the request can access. func getSnapshotsSimple(client *Client, args GetSnapshotsSimpleParams) (*GetSnapshotsSimpleResults, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: "snapshots"} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("GET", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("GET", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 200: break } var results GetSnapshotsSimpleResults json.Unmarshal(body, &results) return &results, nil } type UpdateSnapshotSnapshotParams struct { // Describes the snapshot. Description string `json:"description,omitempty"` // The name of the snapshot. Name string `json:"name,omitempty"` } type UpdateSnapshotParams struct { // Snapshot is required. Snapshot UpdateSnapshotSnapshotParams `json:"snapshot"` // // The unique identifier of the tenant or account. TenantId string `json:"-"` // SnapshotId is required. // // The unique identifier of an existing snapshot. SnapshotId string `json:"-"` } type UpdateSnapshotResults struct { Snapshot Snapshot `json:"snapshot"` } // // Updates a specified snapshot. func updateSnapshot(client *Client, args UpdateSnapshotParams) (*UpdateSnapshotResults, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: fmt.Sprintf("snapshots/%s", args.SnapshotId)} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("PUT", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("PUT", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 200: break } var results UpdateSnapshotResults json.Unmarshal(body, &results) return &results, nil } type ShowSnapshotMetadataParams struct { // // The unique identifier of the tenant or account. TenantId string `json:"-"` // SnapshotId is required. // // The unique identifier of an existing snapshot. SnapshotId string `json:"-"` } type ShowSnapshotMetadataResults struct { Snapshot Snapshot `json:"snapshot"` } // // Shows the metadata for a specified snapshot. func showSnapshotMetadata(client *Client, args ShowSnapshotMetadataParams) (*ShowSnapshotMetadataResults, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: fmt.Sprintf("snapshots/%s/metadata", args.SnapshotId)} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("GET", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("GET", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 200: break } var results ShowSnapshotMetadataResults json.Unmarshal(body, &results) return &results, nil } type GetVolumesDetailParams struct { // // The unique identifier of the tenant or account. TenantId string `json:"-"` } type VolumeAttachment struct { Device string `json:"device"` Id string `json:"id"` ServerId string `json:"server_id"` VolumeId string `json:"volume_id"` } type Volume struct { Attachments []VolumeAttachment `json:"attachments"` AvailabilityZone string `json:"availability_zone"` Bootable string `json:"bootable"` CreatedAt string `json:"created_at"` Description string `json:"description"` ID string `json:"id"` Links []struct { Href string `json:"href"` Rel string `json:"rel"` } `json:"links"` Metadata map[string]string `json:"metadata"` Name string `json:"name"` Os_Vol_Host_Attr_Host string `json:"os-vol-host-attr:host"` Os_Vol_Tenant_Attr_TenantID string `json:"os-vol-tenant-attr:tenant_id"` Size int `json:"size"` SnapshotID interface{} `json:"snapshot_id"` SourceVolid interface{} `json:"source_volid"` Status string `json:"status"` VolumeType string `json:"volume_type"` } type GetVolumesDetailResults struct { Volumes []Volume `json:"volumes"` } // // Lists detailed information for all Block Storage volumes that the tenant who submits the request can access. func getVolumesDetail(client *Client, args GetVolumesDetailParams) (*GetVolumesDetailResults, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: "volumes/detail"} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("GET", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("GET", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 200: break } var results GetVolumesDetailResults json.Unmarshal(body, &results) return &results, nil } type GetVolumeParams struct { // // The unique identifier of the tenant or account. TenantId string `json:"-"` // VolumeId is required. // // The unique identifier of an existing volume. VolumeId string `json:"-"` } type GetVolumeResults struct { Volume Volume `json:"volume"` } // // Shows information about a specified volume. // Preconditions // // The specified volume must exist. : func getVolume(client *Client, args GetVolumeParams) (*GetVolumeResults, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: fmt.Sprintf("volumes/%s", args.VolumeId)} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("GET", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("GET", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 200: break } var results GetVolumeResults json.Unmarshal(body, &results) return &results, nil } type UpdateVolumeVolumeParams struct { Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` } type UpdateVolumeParams struct { // Volume is required. Volume UpdateVolumeVolumeParams `json:"volume"` // // The unique identifier of the tenant or account. TenantId string `json:"-"` // VolumeId is required. // // The unique identifier of an existing volume. VolumeId string `json:"-"` } type UpdateVolumeResults struct { Volume Volume `json:"volume"` } // // Updates a volume. func updateVolume(client *Client, args UpdateVolumeParams) (*UpdateVolumeResults, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: fmt.Sprintf("volumes/%s", args.VolumeId)} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("PUT", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("PUT", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 200: break } var results UpdateVolumeResults json.Unmarshal(body, &results) return &results, nil } type DeleteVolumeParams struct { // // The unique identifier of the tenant or account. TenantId string `json:"-"` // VolumeId is required. // // The unique identifier of an existing volume. VolumeId string `json:"-"` } type DeleteVolumeResults struct { } // // Deletes a specified volume. // Preconditions // // Volume status must be available, in-use, error, or error_restoring. // You cannot already have a snapshot related to the specified volume. // You cannot delete a volume that is in a migration. : // Asynchronous Postconditions // // The volume is deleted in volume index. // The volume managed by OpenStack Block Storage is deleted in storage node. : // Troubleshooting // // If volume status remains in deleting or becomes error_deleting the request failed. Ensure you meet the preconditions then investigate the storage backend. // The volume managed by OpenStack Block Storage is not deleted from the storage system. : func deleteVolume(client *Client, args DeleteVolumeParams) (*DeleteVolumeResults, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: fmt.Sprintf("volumes/%s", args.VolumeId)} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("DELETE", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("DELETE", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 202: break } var results DeleteVolumeResults json.Unmarshal(body, &results) return &results, nil } type CreateVolumeTypeVolumeTypeExtraSpecsParams struct { Capabilities string `json:"capabilities,omitempty"` } type CreateVolumeTypeVolumeTypeParams struct { // The name of the volume type. Name string `json:"name,omitempty"` ExtraSpecs CreateVolumeTypeVolumeTypeExtraSpecsParams `json:"extra_specs,omitempty"` } type CreateVolumeTypeParams struct { // VolumeType is required. // A partial representation of a volume type used in the creation process. VolumeType CreateVolumeTypeVolumeTypeParams `json:"volume_type"` // // The unique identifier of the tenant or account. TenantId string `json:"-"` } type CreateVolumeTypeResults struct { VolumeType VolumeType `json:"volume_type"` } // // Creates a volume type. func createVolumeType(client *Client, args CreateVolumeTypeParams) (*CreateVolumeTypeResults, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: "types"} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("POST", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("POST", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 200: break } var results CreateVolumeTypeResults json.Unmarshal(body, &results) return &results, nil } type CreateSnapshotSnapshotParams struct { // [True/False] Indicate whether to snapshot, even if the volume is attached. Default==False. Force bool `json:"force,omitempty"` // Name of the snapshot. The default is None. Name string `json:"name,omitempty"` // Description of the snapshot. The default is None. Description string `json:"description,omitempty"` // VolumeId is required. // To create a snapshot from an existing volume, specify the ID of the existing volume. VolumeId string `json:"volume_id"` } type CreateSnapshotParams struct { // Snapshot is required. // A partial representation of a snapshot used in the creation process. Snapshot CreateSnapshotSnapshotParams `json:"snapshot"` // // The unique identifier of the tenant or account. TenantId string `json:"-"` } type CreateSnapshotResults struct { Snapshot Snapshot `json:"snapshot"` } // // Creates a snapshot, which is a point-in-time complete copy of a volume. You can create a volume from the snapshot. func createSnapshot(client *Client, args CreateSnapshotParams) (*CreateSnapshotResults, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: "snapshots"} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("POST", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("POST", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 200, 202: break } var results CreateSnapshotResults json.Unmarshal(body, &results) return &results, nil } type GetSnapshotsDetailParams struct { // // The unique identifier of the tenant or account. TenantId string `json:"-"` } type GetSnapshotsDetailResults struct { Snapshots []Snapshot `json:"snapshots"` } // // Lists detailed information for all Block Storage snapshots that the tenant who submits the request can access. func getSnapshotsDetail(client *Client, args GetSnapshotsDetailParams) (*GetSnapshotsDetailResults, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: "snapshots/detail"} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("GET", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("GET", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 200: break } var results GetSnapshotsDetailResults json.Unmarshal(body, &results) return &results, nil } type GetSnapshotParams struct { // // The unique identifier of the tenant or account. TenantId string `json:"-"` // SnapshotId is required. // // The unique identifier of an existing snapshot. SnapshotId string `json:"-"` } type GetSnapshotResults struct { Snapshot Snapshot `json:"snapshot"` } // // Shows information for a specified snapshot. func getSnapshot(client *Client, args GetSnapshotParams) (*GetSnapshotResults, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: fmt.Sprintf("snapshots/%s", args.SnapshotId)} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("GET", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("GET", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 200: break } var results GetSnapshotResults json.Unmarshal(body, &results) return &results, nil } type ListVersionsParams struct { } type Version struct { ID string `json:"id"` Links []struct { Href string `json:"href"` Rel string `json:"rel"` } `json:"links"` Media_Types []struct { Base string `json:"base"` Type string `json:"type"` } `json:"media-types"` Status string `json:"status"` Updated string `json:"updated"` } type ListVersionsResults struct { Versions []Version `json:"versions"` } // // Lists information about all Block Storage API versions. func listVersions(client *Client, args ListVersionsParams) (*ListVersionsResults, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: "../.."} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("GET", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("GET", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 200, 300: break } var results ListVersionsResults json.Unmarshal(body, &results) return &results, nil } type VersionDetailsParams struct { } type VersionDetailsResults struct { Version Version `json:"version"` } // // Shows details for Block Storage API v2. func versionDetails(client *Client, args VersionDetailsParams) (*VersionDetailsResults, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: ".."} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("GET", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("GET", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 200, 203: break } var results VersionDetailsResults json.Unmarshal(body, &results) return &results, nil } type CreateVolumeVolumeParams struct { // To create a volume from an existing volume, specify the ID of the existing volume. If specified, the volume is created with same size of the source volume. SourceVolid string `json:"source_volid,omitempty"` // To create a volume from an existing snapshot, specify the ID of the existing volume snapshot. If specified, the volume is created in same availability zone and with same size of the snapshot. SnapshotId string `json:"snapshot_id,omitempty"` // The ID of the image from which you want to create the volume. Required to create a bootable volume. ImageRef string `json:"imageRef,omitempty"` // The associated volume type. VolumeType string `json:"volume_type,omitempty"` // Enables or disables the bootable attribute. You can boot an instance from a bootable volume. Bootable bool `json:"bootable,omitempty"` // One or more metadata key and value pairs to associate with the volume. Metadata interface{} `json:"metadata,omitempty"` // The availability zone. AvailabilityZone string `json:"availability_zone,omitempty"` // The volume description. Description string `json:"description,omitempty"` // Size is required. // The size of the volume, in GBs. Size int `json:"size"` // The volume name. Name string `json:"name,omitempty"` } type CreateVolumeParams struct { // Volume is required. Volume CreateVolumeVolumeParams `json:"volume"` // // The unique identifier of the tenant or account. TenantId string `json:"-"` } type CreateVolumeResults struct { Volume Volume `json:"volume"` } // // Creates a volume. // To create a bootable volume, include the image ID and set the bootable flag to true in the request body. // Preconditions // // The user must have enough volume storage quota remaining to create a volume of size requested. : // Asynchronous Postconditions // // With correct permissions, you can see the volume status as available through API calls. // With correct access, you can see the created volume in the storage system that OpenStack Block Storage manages. : // Troubleshooting // // If volume status remains creating or shows another error status, the request failed. Ensure you meet the preconditions then investigate the storage backend. // Volume is not created in the storage system which OpenStack Block Storage manages. // The storage node needs enough free storage space to match the specified size of the volume creation request. : func createVolume(client *Client, args CreateVolumeParams) (*CreateVolumeResults, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: "volumes"} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("POST", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("POST", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 200, 202: break } var results CreateVolumeResults json.Unmarshal(body, &results) return &results, nil } type GetVolumesSimpleParams struct { // // The unique identifier of the tenant or account. TenantId string `json:"-"` } type GetVolumesSimpleResults struct { Volumes []Volume `json:"volumes"` } // // Lists summary information for all Block Storage volumes that the tenant who submits the request can access. func getVolumesSimple(client *Client, args GetVolumesSimpleParams) (*GetVolumesSimpleResults, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: "volumes"} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("GET", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("GET", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 200: break } var results GetVolumesSimpleResults json.Unmarshal(body, &results) return &results, nil } type GetVolumeTypeParams struct { // // The unique identifier of the tenant or account. TenantId string `json:"-"` // VolumeTypeId is required. // // The unique identifier for an existing volume type. VolumeTypeId string `json:"-"` } type GetVolumeTypeResults struct { VolumeType VolumeType `json:"volume_type"` } // // Shows information about a specified volume type. func getVolumeType(client *Client, args GetVolumeTypeParams) (*GetVolumeTypeResults, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: fmt.Sprintf("types/%s", args.VolumeTypeId)} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("GET", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("GET", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 200: break } var results GetVolumeTypeResults json.Unmarshal(body, &results) return &results, nil } type DeleteSnapshotParams struct { // // The unique identifier of the tenant or account. TenantId string `json:"-"` // SnapshotId is required. // // The unique identifier of an existing snapshot. SnapshotId string `json:"-"` } type DeleteSnapshotResults struct { } // // Deletes a specified snapshot. func deleteSnapshot(client *Client, args DeleteSnapshotParams) (*DeleteSnapshotResults, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: fmt.Sprintf("snapshots/%s", args.SnapshotId)} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("DELETE", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("DELETE", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 202: break } var results DeleteSnapshotResults json.Unmarshal(body, &results) return &results, nil } type ListExtensionsCinderV2Params struct { } type Extension struct { Alias string `json:"alias"` Description string `json:"description"` Links []interface{} `json:"links"` Name string `json:"name"` Namespace string `json:"namespace"` Updated string `json:"updated"` } type ListExtensionsCinderV2Results struct { Extensions []Extension `json:"extensions"` } // // Lists Block Storage API extensions. func listExtensionsCinderV2(client *Client, args ListExtensionsCinderV2Params) (*ListExtensionsCinderV2Results, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: ".."} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("GET", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("GET", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 200, 300: break } var results ListExtensionsCinderV2Results json.Unmarshal(body, &results) return &results, nil } type GetVolumeTypesParams struct { // // The unique identifier of the tenant or account. TenantId string `json:"-"` } type GetVolumeTypesResults struct { VolumeTypes []VolumeType `json:"volume_types"` } // // Lists volume types. func getVolumeTypes(client *Client, args GetVolumeTypesParams) (*GetVolumeTypesResults, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: "types"} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("GET", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("GET", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 200: break } var results GetVolumeTypesResults json.Unmarshal(body, &results) return &results, nil } type DeleteVolumeTypeParams struct { // // The unique identifier of the tenant or account. TenantId string `json:"-"` // VolumeTypeId is required. // // The unique identifier for an existing volume type. VolumeTypeId string `json:"-"` } type DeleteVolumeTypeResults struct { } // // Deletes a specified volume type. func deleteVolumeType(client *Client, args DeleteVolumeTypeParams) (*DeleteVolumeTypeResults, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: fmt.Sprintf("types/%s", args.VolumeTypeId)} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("DELETE", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("DELETE", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 202: break } var results DeleteVolumeTypeResults json.Unmarshal(body, &results) return &results, nil } type UpdateSnapshotMetadataMetadataParams struct { // Key is required. Key string `json:"key"` } type UpdateSnapshotMetadataParams struct { // Metadata is required. // One or more metadata key and value pairs to set or unset for the snapshot. To unset a metadata key value, specify only the key name. To set a metadata key value, specify the key and value pair. The Block Storage server does not respect case-sensitive key names. For example, if you specify both "key": "v1" and "KEY": "V1", the server sets and returns only the KEY key and value pair. Metadata UpdateSnapshotMetadataMetadataParams `json:"metadata"` // // The unique identifier of the tenant or account. TenantId string `json:"-"` // SnapshotId is required. // // The unique identifier of an existing snapshot. SnapshotId string `json:"-"` } type UpdateSnapshotMetadataResults struct { Metadata struct { Key string `json:"key"` } `json:"metadata"` } // // Updates the metadata for a specified snapshot. func updateSnapshotMetadata(client *Client, args UpdateSnapshotMetadataParams) (*UpdateSnapshotMetadataResults, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: fmt.Sprintf("snapshots/%s/metadata", args.SnapshotId)} url := client.endpoint.ResolveReference(&urlPath).String() var req *http.Request if string(argsAsJson) != "{}" { req, err = http.NewRequest("PUT", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest("PUT", url, nil) if err != nil { return nil, err } } resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 200: break } var results UpdateSnapshotMetadataResults json.Unmarshal(body, &results) return &results, nil } type UpdateVolumeMetadataParams struct { // Key-value pairs to set in the volume metadata. Metadata map[string]string `json:"metadata"` } // Updates the metadata for a specified volume. func updateVolumeMetadata(client *Client, volumeId string, args *UpdateVolumeMetadataParams) (*UpdateVolumeMetadataParams, error) { argsAsJson, err := json.Marshal(args) if err != nil { return nil, err } urlPath := url.URL{Path: fmt.Sprintf("volumes/%s/metadata", volumeId)} url := client.endpoint.ResolveReference(&urlPath).String() // Contrary to what the documentation says, using POST here means // that we can update a single key. Using PUT will always // overwrite the entire metadata, throwing away keys it they // aren't in the request. // https://developer.openstack.org/api-ref/block-storage/v3/?expanded=update-a-volume-s-metadata-detail#update-a-volume-s-metadata req, err := http.NewRequest("POST", url, bytes.NewBuffer(argsAsJson)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") resp, err := client.handleRequest(req) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } switch resp.StatusCode { default: return nil, fmt.Errorf("invalid status (%d): %s", resp.StatusCode, body) case 200: break } var results UpdateVolumeMetadataParams json.Unmarshal(body, &results) return &results, nil } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/cinder/client.go000066400000000000000000000251641414605405700231750ustar00rootroot00000000000000// Copyright 2015 Canonical Ltd. // Licensed under the LGPLv3, see LICENCE file for details. package cinder import ( "fmt" "net/http" "net/url" "time" ) // Basic returns a basic Cinder client which will handle authorization // of requests, and routing to the correct endpoint. func Basic(endpoint *url.URL, tenantId string, token TokenFn) *Client { return NewClient(tenantId, endpoint, SetAuthHeaderFn(token, http.DefaultClient.Do), ) } // TokenFn represents a function signature which returns the user's // authorization token when called. type TokenFn func() string // SetAuthHeaderFn returns a RequestHandlerFn which sets the // authentication headers for a given request. func SetAuthHeaderFn(token TokenFn, wrappedHandler RequestHandlerFn) RequestHandlerFn { return func(req *http.Request) (*http.Response, error) { req.Header.Set("X-Auth-Token", token()) return wrappedHandler(req) } } // NewClient is the most flexible way to instantiate a Cinder // Client. The handleRequest function will be responsible for // modifying requests and dispatching them as needed. For an example // of how to utilize this method, see the Basic function. func NewClient(tenantId string, endpoint *url.URL, handleRequest RequestHandlerFn) *Client { if endpoint == nil { return nil } // Ensure the cinder endpoint has a trailing slash on the path. path := endpoint.Path if len(path) != 0 && path[len(path)-1:] != "/" { changedEndpoint := *endpoint changedEndpoint.Path += "/" endpoint = &changedEndpoint } return &Client{tenantId, endpoint, handleRequest} } // Client is a Cinder client. type Client struct { tenantId string endpoint *url.URL handleRequest RequestHandlerFn } // GetSnapshot shows information for a specified snapshot. func (c *Client) GetSnapshot(snapshotId string) (*GetSnapshotResults, error) { return getSnapshot( c, GetSnapshotParams{TenantId: c.tenantId, SnapshotId: snapshotId}, ) } // UpdateSnapshot updates a specified snapshot. func (c *Client) UpdateSnapshot(snapshotId string, args UpdateSnapshotSnapshotParams) (*UpdateSnapshotResults, error) { return updateSnapshot(c, UpdateSnapshotParams{ TenantId: c.tenantId, SnapshotId: snapshotId, Snapshot: args, }) } // DeleteSnapshot deletes a specified snapshot. func (c *Client) DeleteSnapshot(snapshotId string) error { _, err := deleteSnapshot( c, DeleteSnapshotParams{TenantId: c.tenantId, SnapshotId: snapshotId}, ) return err } // VersionDetails shows details for Block Storage API v2. func (c *Client) VersionDetails() (*VersionDetailsResults, error) { return versionDetails(c, VersionDetailsParams{}) } // ListExtensionsCinderV2 lists Block Storage API extensions. func (c *Client) ListExtensionsCinderV2() (*ListExtensionsCinderV2Results, error) { return listExtensionsCinderV2(c, ListExtensionsCinderV2Params{}) } // GetVolumesSimple lists summary information for all Block Storage // volumes that the tenant who submits the request can access. func (c *Client) GetVolumesSimple() (*GetVolumesSimpleResults, error) { return getVolumesSimple(c, GetVolumesSimpleParams{TenantId: c.tenantId}) } // UpdateVolumeType updates a volume type. func (c *Client) UpdateVolumeType(volumeTypeId, volumeType string) (*UpdateVolumeTypeResults, error) { return updateVolumeType(c, UpdateVolumeTypeParams{ TenantId: c.tenantId, VolumeTypeId: volumeTypeId, VolumeType: volumeType, }) } // DeleteVolumeType deletes a specified volume type. func (c *Client) DeleteVolumeType(volumeTypeId string) error { _, err := deleteVolumeType( c, DeleteVolumeTypeParams{TenantId: c.tenantId, VolumeTypeId: volumeTypeId}, ) return err } // GetVolumesDetail lists detailed information for all Block Storage // volumes that the tenant who submits the request can access. func (c *Client) GetVolumesDetail() (*GetVolumesDetailResults, error) { return getVolumesDetail(c, GetVolumesDetailParams{TenantId: c.tenantId}) } // GetVolume lists information about the volume with the given // volumeId. func (c *Client) GetVolume(volumeId string) (*GetVolumeResults, error) { return getVolume(c, GetVolumeParams{TenantId: c.tenantId, VolumeId: volumeId}) } // CreateVolumeType creates a volume type. func (c *Client) CreateVolumeType(args CreateVolumeTypeVolumeTypeParams) (*CreateVolumeTypeResults, error) { return createVolumeType( c, CreateVolumeTypeParams{TenantId: c.tenantId, VolumeType: args}, ) } // GetVolumeType shows information about a specified volume type. func (c *Client) GetVolumeType(volumeTypeId string) (*GetVolumeTypeResults, error) { return getVolumeType( c, GetVolumeTypeParams{TenantId: c.tenantId, VolumeTypeId: volumeTypeId}, ) } // ListVersion lists information about all Block Storage API versions. func (c *Client) ListVersions() (*ListVersionsResults, error) { return listVersions(c, ListVersionsParams{}) } // UpdateVolumeTypeExtraSpecs updates the extra specifications // assigned to a volume type. func (c *Client) UpdateVolumeTypeExtraSpecs(volumeTypeId, volumeType, extraSpecs string) (*UpdateVolumeTypeExtraSpecsResults, error) { return updateVolumeTypeExtraSpecs(c, UpdateVolumeTypeExtraSpecsParams{ TenantId: c.tenantId, VolumeTypeId: volumeTypeId, VolumeType: volumeType, ExtraSpecs: extraSpecs, }) } // GetSnapshotsSimple lists summary information for all Block Storage // snapshots that the tenant who submits the request can access. func (c *Client) GetSnapshotsSimple() (*GetSnapshotsSimpleResults, error) { return getSnapshotsSimple(c, GetSnapshotsSimpleParams{TenantId: c.tenantId}) } // ShowSnapshotMetadata shows the metadata for a specified snapshot. func (c *Client) ShowSnapshotMetadata(snapshotId string) (*ShowSnapshotMetadataResults, error) { return showSnapshotMetadata( c, ShowSnapshotMetadataParams{TenantId: c.tenantId, SnapshotId: snapshotId}, ) } // CreateSnapshot creates a snapshot, which is a point-in-time // complete copy of a volume. You can create a volume from the // snapshot. func (c *Client) CreateSnapshot(args CreateSnapshotSnapshotParams) (*CreateSnapshotResults, error) { return createSnapshot(c, CreateSnapshotParams{TenantId: c.tenantId, Snapshot: args}) } // GetSnapshotsDetail lists detailed information for all Block Storage // snapshots that the tenant who submits the request can access. func (c *Client) GetSnapshotsDetail() (*GetSnapshotsDetailResults, error) { return getSnapshotsDetail(c, GetSnapshotsDetailParams{TenantId: c.tenantId}) } // UpdateSnapshotMetadata updates the metadata for a specified // snapshot. func (c *Client) UpdateSnapshotMetadata(snapshotId, key string) (*UpdateSnapshotMetadataResults, error) { return updateSnapshotMetadata(c, UpdateSnapshotMetadataParams{ TenantId: c.tenantId, SnapshotId: snapshotId, Metadata: UpdateSnapshotMetadataMetadataParams{ Key: key, }, }) } // CreateVolume creates a volume. To create a bootable volume, include // the image ID and set the bootable flag to true in the request body. // // Preconditions: // // - The user must have enough volume storage quota remaining to create // a volume of size requested. // // Asynchronous Postconditions: // // - With correct permissions, you can see the volume status as // available through API calls. // - With correct access, you can see the created volume in the // storage system that OpenStack Block Storage manages. // // Troubleshooting: // // - If volume status remains creating or shows another error status, // the request failed. Ensure you meet the preconditions then // investigate the storage backend. // - Volume is not created in the storage system which OpenStack Block Storage manages. // - The storage node needs enough free storage space to match the // specified size of the volume creation request. func (c *Client) CreateVolume(args CreateVolumeVolumeParams) (*CreateVolumeResults, error) { return createVolume(c, CreateVolumeParams{TenantId: c.tenantId, Volume: args}) } // UpdateVolume updates a volume. func (c *Client) UpdateVolume(volumeId string, args UpdateVolumeVolumeParams) (*UpdateVolumeResults, error) { return updateVolume(c, UpdateVolumeParams{TenantId: c.tenantId, VolumeId: volumeId, Volume: args}) } // DeleteVolume flags a volume for deletion. The volume managed by // OpenStack Block Storage is not deleted from the storage system. func (c *Client) DeleteVolume(volumeId string) error { _, err := deleteVolume( c, DeleteVolumeParams{TenantId: c.tenantId, VolumeId: volumeId}, ) return err } // GetVolumeTypes lists volume types. func (c *Client) GetVolumeTypes() (*GetVolumeTypesResults, error) { return getVolumeTypes(c, GetVolumeTypesParams{TenantId: c.tenantId}) } type predicateFn func() (bool, error) func notifier(predicate predicateFn, numAttempts int, waitDur time.Duration) <-chan error { notifierChan := make(chan error) go func() { defer close(notifierChan) for attemptNum := 0; attemptNum < numAttempts; attemptNum++ { if ok, err := predicate(); err != nil { notifierChan <- err return } else if ok { return } time.Sleep(waitDur) } notifierChan <- fmt.Errorf("too many attempts") }() return notifierChan } // VolumeStatusNotifier will check a volume's status to determine // whether it matches the given status. After a check, it waits for // "waitDur" before attempting again. If the status does not match // after "numAttempts", an error is returned. func (c *Client) VolumeStatusNotifier(volId, status string, numAttempts int, waitDur time.Duration) <-chan error { statusMatches := func() (bool, error) { volInfo, err := c.GetVolume(volId) if err != nil { return false, err } return volInfo.Volume.Status == status, nil } return notifier(statusMatches, numAttempts, waitDur) } // SnapshotStatusNotifier will check a volume's status to determine // whether it matches the given status. After a check, it waits for // "waitDur" before attempting again. If the status does not match // after "numAttempts", an error is returned. func (c *Client) SnapshotStatusNotifier(snapId, status string, numAttempts int, waitDur time.Duration) <-chan error { statusMatches := func() (bool, error) { snapInfo, err := c.GetSnapshot(snapId) if err != nil { return false, err } return snapInfo.Snapshot.Status == status, nil } return notifier(statusMatches, numAttempts, waitDur) } // SetVolumeMetadata sets metadata on a server. Replaces metadata // items that match keys - doesn't modify items that aren't in the // request. Returns the complete, updated metadata for the volume. func (c *Client) SetVolumeMetadata(volumeId string, metadata map[string]string) (map[string]string, error) { response, err := updateVolumeMetadata(c, volumeId, &UpdateVolumeMetadataParams{metadata}) if err != nil { return nil, err } return response.Metadata, nil } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/cinder/client_test.go000066400000000000000000000571171414605405700242370ustar00rootroot00000000000000// Copyright 2015 Canonical Ltd. // Licensed under the LGPLv3, see LICENCE file for details. package cinder import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "net/url" gc "gopkg.in/check.v1" ) const ( testId = "test-id" testToken = "test-token" testTime = "test-time" testDescr = "test-description" testName = "test-name" testAttrProgr = "test-attribute-prog" testAttrProj = "test-attribute-proj" testStatus = "test-status" ) var _ = gc.Suite(&CinderTestSuite{}) type CinderTestSuite struct { client *Client *http.ServeMux } func (s *CinderTestSuite) SetUpSuite(c *gc.C) { if *live { return } endpoint, err := url.Parse("http://volume.testing/v2/" + testId) c.Assert(err, gc.IsNil) cinderClient := NewClient( testId, endpoint, SetAuthHeaderFn(func() string { return testToken }, s.localDo), ) s.client = cinderClient } func (s *CinderTestSuite) SetUpTest(c *gc.C) { if *live { c.Skip("Only running live tests.") } // We want a fresh Muxer so that any paths that aren't explicitly // set up by the test are treated as errors. s.ServeMux = http.NewServeMux() } func (s *CinderTestSuite) TestCreateSnapshot(c *gc.C) { snapReq := CreateSnapshotSnapshotParams{ VolumeId: testId, Name: testName, Description: testDescr, } numCalls := 0 s.HandleFunc("/v2/"+testId+"/snapshots", func(w http.ResponseWriter, req *http.Request) { numCalls++ c.Check(req.Header["X-Auth-Token"], gc.DeepEquals, []string{testToken}) reqBody, err := ioutil.ReadAll(req.Body) c.Assert(err, gc.IsNil) var receivedReq CreateSnapshotParams err = json.Unmarshal(reqBody, &receivedReq) c.Assert(err, gc.IsNil) c.Check(receivedReq, gc.DeepEquals, CreateSnapshotParams{Snapshot: snapReq}) resp := Snapshot{ CreatedAt: "test-time", Description: receivedReq.Snapshot.Description, ID: "test-id", Name: receivedReq.Snapshot.Name, Size: 1, Status: "test-status", VolumeID: receivedReq.Snapshot.VolumeId, } respBody, err := json.Marshal(&CreateSnapshotResults{Snapshot: resp}) c.Assert(err, gc.IsNil) w.(*responseWriter).Body = ioutil.NopCloser(bytes.NewReader(respBody)) w.(*responseWriter).StatusCode = 202 }) resp, err := s.client.CreateSnapshot(snapReq) c.Assert(numCalls, gc.Equals, 1) c.Assert(err, gc.IsNil) c.Check(resp.Snapshot.CreatedAt, gc.Equals, testTime) c.Check(resp.Snapshot.Description, gc.Equals, snapReq.Description) c.Check(resp.Snapshot.ID, gc.Equals, testId) c.Check(resp.Snapshot.Name, gc.Equals, snapReq.Name) c.Check(resp.Snapshot.Size, gc.Equals, 1) c.Check(resp.Snapshot.Status, gc.Equals, testStatus) c.Check(resp.Snapshot.VolumeID, gc.Equals, snapReq.VolumeId) } func (s *CinderTestSuite) TestDeleteSnapshot(c *gc.C) { numCalls := 0 s.HandleFunc("/v2/"+testId+"/snapshots/"+testId, func(w http.ResponseWriter, req *http.Request) { numCalls++ w.(*responseWriter).Body = ioutil.NopCloser(bytes.NewReader([]byte{})) w.(*responseWriter).StatusCode = 202 }) err := s.client.DeleteSnapshot(testId) c.Assert(numCalls, gc.Equals, 1) c.Assert(err, gc.IsNil) } func (s *CinderTestSuite) TestGetSnapshot(c *gc.C) { numCalls := 0 s.HandleFunc("/v2/"+testId+"/snapshots/"+testId, func(w http.ResponseWriter, req *http.Request) { numCalls++ c.Check(req.Header["X-Auth-Token"], gc.DeepEquals, []string{testToken}) resp := Snapshot{ CreatedAt: testTime, Description: testDescr, ID: testId, Name: testName, Os_Extended_Snapshot_Attributes_Progress: testAttrProgr, Os_Extended_Snapshot_Attributes_ProjectID: testAttrProj, Size: 1, Status: testStatus, VolumeID: testId, } respBody, err := json.Marshal(&GetSnapshotResults{Snapshot: resp}) c.Assert(err, gc.IsNil) w.(*responseWriter).Response.StatusCode = 200 w.(*responseWriter).Body = ioutil.NopCloser(bytes.NewReader(respBody)) }) // Test GetSnapshot getResp, err := s.client.GetSnapshot(testId) c.Assert(err, gc.IsNil) c.Assert(numCalls, gc.Equals, 1) c.Check(getResp.Snapshot.CreatedAt, gc.Not(gc.HasLen), 0) c.Check(getResp.Snapshot.Description, gc.Equals, testDescr) c.Check(getResp.Snapshot.ID, gc.Not(gc.HasLen), 0) c.Check(getResp.Snapshot.Name, gc.Equals, testName) c.Check(getResp.Snapshot.Size, gc.Equals, 1) c.Check(getResp.Snapshot.Status, gc.Equals, testStatus) c.Check(getResp.Snapshot.VolumeID, gc.Equals, testId) } func (s *CinderTestSuite) TestGetSnapshotDetail(c *gc.C) { numCalls := 0 s.HandleFunc("/v2/"+testId+"/snapshots/detail", func(w http.ResponseWriter, req *http.Request) { numCalls++ c.Check(req.Header["X-Auth-Token"], gc.DeepEquals, []string{testToken}) resp := []Snapshot{{ CreatedAt: testTime, Description: testDescr, ID: testId, Name: testName, Os_Extended_Snapshot_Attributes_Progress: testAttrProgr, Os_Extended_Snapshot_Attributes_ProjectID: testAttrProj, Size: 1, Status: testStatus, VolumeID: testId, }} respBody, err := json.Marshal(&GetSnapshotsDetailResults{Snapshots: resp}) c.Assert(err, gc.IsNil) w.(*responseWriter).Response.StatusCode = 200 w.(*responseWriter).Body = ioutil.NopCloser(bytes.NewReader(respBody)) }) detailGetResp, err := s.client.GetSnapshotsDetail() c.Assert(err, gc.IsNil) c.Assert(detailGetResp.Snapshots, gc.HasLen, 1) c.Assert(numCalls, gc.Equals, 1) snapshot := detailGetResp.Snapshots[0] c.Check(snapshot.CreatedAt, gc.Equals, testTime) c.Check(snapshot.Description, gc.Equals, testDescr) c.Check(snapshot.ID, gc.Equals, testId) c.Check(snapshot.Name, gc.Equals, testName) c.Check(snapshot.Os_Extended_Snapshot_Attributes_Progress, gc.Equals, testAttrProgr) c.Check(snapshot.Os_Extended_Snapshot_Attributes_ProjectID, gc.Equals, testAttrProj) c.Check(snapshot.Size, gc.Equals, 1) c.Check(snapshot.Status, gc.Equals, testStatus) c.Check(snapshot.VolumeID, gc.Equals, testId) } func (s *CinderTestSuite) TestGetSnapshotSimple(c *gc.C) { numCalls := 0 s.HandleFunc("/v2/"+testId+"/snapshots", func(w http.ResponseWriter, req *http.Request) { numCalls++ c.Check(req.Header["X-Auth-Token"], gc.DeepEquals, []string{testToken}) resp := []Snapshot{{ CreatedAt: testTime, Description: testDescr, ID: testId, Name: testName, Os_Extended_Snapshot_Attributes_Progress: testAttrProgr, Os_Extended_Snapshot_Attributes_ProjectID: testAttrProj, Size: 1, Status: testStatus, VolumeID: testId, }} respBody, err := json.Marshal(&GetSnapshotsDetailResults{Snapshots: resp}) c.Assert(err, gc.IsNil) w.(*responseWriter).Response.StatusCode = 200 w.(*responseWriter).Body = ioutil.NopCloser(bytes.NewReader(respBody)) }) snapshotSimpResp, err := s.client.GetSnapshotsSimple() c.Assert(numCalls, gc.Equals, 1) c.Assert(err, gc.IsNil) c.Assert(snapshotSimpResp.Snapshots, gc.HasLen, 1) snapshot := snapshotSimpResp.Snapshots[0] c.Check(snapshot.CreatedAt, gc.Equals, testTime) c.Check(snapshot.Description, gc.Equals, testDescr) c.Check(snapshot.ID, gc.Equals, testId) c.Check(snapshot.Name, gc.Equals, testName) c.Check(snapshot.Size, gc.Equals, 1) c.Check(snapshot.Status, gc.Equals, testStatus) c.Check(snapshot.VolumeID, gc.Equals, testId) } func (s *CinderTestSuite) TestShowSnapshotMetadata(c *gc.C) { numCalls := 0 s.HandleFunc("/v2/"+testId+"/snapshots/"+testId+"/metadata", func(w http.ResponseWriter, req *http.Request) { numCalls++ c.Check(req.Header["X-Auth-Token"], gc.DeepEquals, []string{testToken}) resp := Snapshot{ CreatedAt: testTime, Description: testDescr, ID: testId, Name: testName, Os_Extended_Snapshot_Attributes_Progress: testAttrProgr, Os_Extended_Snapshot_Attributes_ProjectID: testAttrProj, Size: 1, Status: testStatus, VolumeID: testId, } respBody, err := json.Marshal(&ShowSnapshotMetadataResults{Snapshot: resp}) c.Assert(err, gc.IsNil) w.(*responseWriter).Response.StatusCode = 200 w.(*responseWriter).Body = ioutil.NopCloser(bytes.NewReader(respBody)) }) resp, err := s.client.ShowSnapshotMetadata(testId) c.Assert(err, gc.IsNil) c.Check(resp.Snapshot.CreatedAt, gc.Equals, testTime) c.Check(resp.Snapshot.Description, gc.Equals, testDescr) c.Check(resp.Snapshot.ID, gc.Equals, testId) c.Check(resp.Snapshot.Name, gc.Equals, testName) c.Check(resp.Snapshot.Size, gc.Equals, 1) c.Check(resp.Snapshot.Status, gc.Equals, testStatus) c.Check(resp.Snapshot.VolumeID, gc.Equals, testId) } func (s *CinderTestSuite) TestUpdateSnapshot(c *gc.C) { updateReq := UpdateSnapshotSnapshotParams{testName, testDescr} numCalls := 0 s.HandleFunc("/v2/"+testId+"/snapshots/"+testId, func(w http.ResponseWriter, req *http.Request) { numCalls++ c.Check(req.Header["X-Auth-Token"], gc.DeepEquals, []string{testToken}) reqBody, err := ioutil.ReadAll(req.Body) c.Assert(err, gc.IsNil) var receivedReq UpdateSnapshotParams err = json.Unmarshal(reqBody, &receivedReq) c.Assert(err, gc.IsNil) c.Check(receivedReq, gc.DeepEquals, UpdateSnapshotParams{Snapshot: updateReq}) resp := Snapshot{ CreatedAt: testTime, Description: updateReq.Description, ID: testId, Name: updateReq.Name, Size: 1, Status: testStatus, VolumeID: testId, } respBody, err := json.Marshal(&UpdateSnapshotResults{Snapshot: resp}) c.Assert(err, gc.IsNil) w.(*responseWriter).Response.StatusCode = 200 w.(*responseWriter).Body = ioutil.NopCloser(bytes.NewReader(respBody)) }) resp, err := s.client.UpdateSnapshot(testId, updateReq) c.Assert(numCalls, gc.Equals, 1) c.Assert(err, gc.IsNil) c.Check(resp.Snapshot.CreatedAt, gc.Equals, testTime) c.Check(resp.Snapshot.Description, gc.Equals, updateReq.Description) c.Check(resp.Snapshot.ID, gc.Equals, testId) c.Check(resp.Snapshot.Name, gc.Equals, updateReq.Name) c.Check(resp.Snapshot.Size, gc.Equals, 1) c.Check(resp.Snapshot.Status, gc.Equals, testStatus) c.Check(resp.Snapshot.VolumeID, gc.Equals, testId) } func (s *CinderTestSuite) TestUpdateSnapshotMetadata(c *gc.C) { numCalls := 0 s.HandleFunc("/v2/"+testId+"/snapshots/"+testId+"/metadata", func(w http.ResponseWriter, req *http.Request) { numCalls++ c.Check(req.Header["X-Auth-Token"], gc.DeepEquals, []string{testToken}) reqBody, err := ioutil.ReadAll(req.Body) c.Assert(err, gc.IsNil) var receivedReq UpdateSnapshotMetadataParams err = json.Unmarshal(reqBody, &receivedReq) c.Assert(err, gc.IsNil) c.Check(receivedReq.Metadata.Key, gc.DeepEquals, "test-key") resp := struct { Key string `json:"key"` }{ Key: receivedReq.Metadata.Key, } respBody, err := json.Marshal(&UpdateSnapshotMetadataResults{Metadata: resp}) c.Assert(err, gc.IsNil) w.(*responseWriter).Response.StatusCode = 200 w.(*responseWriter).Body = ioutil.NopCloser(bytes.NewReader(respBody)) }) resp, err := s.client.UpdateSnapshotMetadata(testId, "test-key") c.Assert(numCalls, gc.Equals, 1) c.Assert(err, gc.IsNil) c.Check(resp.Metadata.Key, gc.Equals, "test-key") } func (s *CinderTestSuite) TestCreateVolume(c *gc.C) { req := CreateVolumeVolumeParams{ Description: testDescr, Size: 1, Name: testName, } numCalls := 0 s.HandleFunc("/v2/"+testId+"/volumes", func(w http.ResponseWriter, req *http.Request) { numCalls++ reqBody, err := ioutil.ReadAll(req.Body) c.Assert(err, gc.IsNil) var receivedReq CreateVolumeParams err = json.Unmarshal(reqBody, &receivedReq) c.Assert(err, gc.IsNil) resp := Volume{ AvailabilityZone: receivedReq.Volume.AvailabilityZone, Bootable: fmt.Sprintf("%v", receivedReq.Volume.Bootable), CreatedAt: "test-time", Description: receivedReq.Volume.Description, Name: receivedReq.Volume.Name, Size: receivedReq.Volume.Size, SnapshotID: testId, SourceVolid: testId, Status: testStatus, VolumeType: "test-volume-type", } respBody, err := json.Marshal(&CreateVolumeResults{Volume: resp}) c.Assert(err, gc.IsNil) w.(*responseWriter).Response.StatusCode = 202 w.(*responseWriter).Body = ioutil.NopCloser(bytes.NewReader(respBody)) c.Assert(w.(*responseWriter).Body, gc.NotNil) }) resp, err := s.client.CreateVolume(req) c.Assert(numCalls, gc.Equals, 1) c.Assert(err, gc.IsNil) c.Check(resp.Volume.Name, gc.Equals, req.Name) c.Check(resp.Volume.AvailabilityZone, gc.Equals, req.AvailabilityZone) c.Check(resp.Volume.Bootable, gc.Equals, fmt.Sprintf("%v", req.Bootable)) c.Check(resp.Volume.Description, gc.Equals, req.Description) c.Check(resp.Volume.Size, gc.Equals, req.Size) c.Check(resp.Volume.SnapshotID, gc.Equals, testId) c.Check(resp.Volume.SourceVolid, gc.Equals, testId) c.Check(resp.Volume.Status, gc.Equals, testStatus) c.Check(resp.Volume.VolumeType, gc.Equals, "test-volume-type") } func (s *CinderTestSuite) TestDeleteVolume(c *gc.C) { numCalls := 0 s.HandleFunc("/v2/"+testId+"/volumes/"+testId, func(w http.ResponseWriter, req *http.Request) { numCalls++ w.(*responseWriter).Response.StatusCode = 202 w.(*responseWriter).Body = ioutil.NopCloser(bytes.NewBuffer([]byte{})) }) err := s.client.DeleteVolume(testId) c.Assert(numCalls, gc.Equals, 1) c.Assert(err, gc.IsNil) } func (s *CinderTestSuite) TestUpdateVolume(c *gc.C) { updateReq := UpdateVolumeVolumeParams{testName, testDescr} numCalls := 0 s.HandleFunc("/v2/"+testId+"/volumes/"+testId, func(w http.ResponseWriter, req *http.Request) { numCalls++ reqBody, err := ioutil.ReadAll(req.Body) c.Assert(err, gc.IsNil) var receivedReq UpdateVolumeParams err = json.Unmarshal(reqBody, &receivedReq) c.Assert(err, gc.IsNil) resp := Volume{ AvailabilityZone: "test-avail-zone", Bootable: "false", CreatedAt: "test-time", Description: receivedReq.Volume.Description, Name: receivedReq.Volume.Name, Size: 1, SnapshotID: testId, SourceVolid: testId, Status: testStatus, VolumeType: "test-volume-type", } respBody, err := json.Marshal(&CreateVolumeResults{Volume: resp}) c.Assert(err, gc.IsNil) w.(*responseWriter).Response.StatusCode = 200 w.(*responseWriter).Body = ioutil.NopCloser(bytes.NewReader(respBody)) c.Assert(w.(*responseWriter).Body, gc.NotNil) }) resp, err := s.client.UpdateVolume(testId, updateReq) c.Assert(numCalls, gc.Equals, 1) c.Assert(err, gc.IsNil) c.Check(resp.Volume.Name, gc.Equals, updateReq.Name) c.Check(resp.Volume.AvailabilityZone, gc.Equals, "test-avail-zone") c.Check(resp.Volume.Description, gc.Equals, updateReq.Description) c.Check(resp.Volume.Size, gc.Equals, 1) c.Check(resp.Volume.SnapshotID, gc.Equals, testId) c.Check(resp.Volume.SourceVolid, gc.Equals, testId) c.Check(resp.Volume.Status, gc.Equals, testStatus) c.Check(resp.Volume.VolumeType, gc.Equals, "test-volume-type") } func (s *CinderTestSuite) TestUpdateVolumeType(c *gc.C) { numCalls := 0 s.HandleFunc("/v2/"+testId+"/types/"+testId, func(w http.ResponseWriter, req *http.Request) { numCalls++ resp := struct { ExtraSpecs struct { Capabilities string `json:"capabilities"` } `json:"extra_specs"` ID string `json:"id"` Name string `json:"name"` }{ ID: testId, Name: testName, } respBody, err := json.Marshal(&UpdateVolumeTypeResults{VolumeType: resp}) c.Assert(err, gc.IsNil) w.(*responseWriter).Response.StatusCode = 200 w.(*responseWriter).Body = ioutil.NopCloser(bytes.NewBuffer(respBody)) }) resp, err := s.client.UpdateVolumeType(testId, "test-volume-type") c.Assert(numCalls, gc.Equals, 1) c.Assert(err, gc.IsNil) c.Check(resp.VolumeType.Name, gc.Equals, testName) c.Check(resp.VolumeType.ID, gc.Equals, testId) } func (s *CinderTestSuite) TestUpdateVolumeTypeExtraSpecs(c *gc.C) { const ( testType = "test-type" testSpecs = "test-xtra-specs" ) numCalls := 0 s.HandleFunc("/v2/"+testId+"/types/"+testId, func(w http.ResponseWriter, req *http.Request) { numCalls++ reqBody, err := ioutil.ReadAll(req.Body) c.Assert(err, gc.IsNil) var receivedReq UpdateVolumeTypeExtraSpecsParams err = json.Unmarshal(reqBody, &receivedReq) c.Assert(err, gc.IsNil) c.Check(receivedReq.ExtraSpecs, gc.Equals, testSpecs) c.Check(receivedReq.VolumeType, gc.Equals, testType) resp := struct { ExtraSpecs struct { Capabilities string `json:"capabilities"` } `json:"extra_specs"` ID string `json:"id"` Name string `json:"name"` }{ ID: testId, Name: testName, ExtraSpecs: struct { Capabilities string `json:"capabilities"` }{ Capabilities: testSpecs, }, } respBody, err := json.Marshal(&UpdateVolumeTypeResults{VolumeType: resp}) c.Assert(err, gc.IsNil) w.(*responseWriter).Response.StatusCode = 200 w.(*responseWriter).Body = ioutil.NopCloser(bytes.NewBuffer(respBody)) }) resp, err := s.client.UpdateVolumeTypeExtraSpecs(testId, testType, testSpecs) c.Assert(numCalls, gc.Equals, 1) c.Assert(err, gc.IsNil) c.Check(resp.VolumeType.ExtraSpecs.Capabilities, gc.Equals, testSpecs) c.Check(resp.VolumeType.ID, gc.Equals, testId) c.Check(resp.VolumeType.Name, gc.Equals, testName) } func (s *CinderTestSuite) TestGetVolumesDetail(c *gc.C) { numCalls := 0 s.HandleFunc("/v2/"+testId+"/volumes/", func(w http.ResponseWriter, req *http.Request) { numCalls++ resp := []Volume{{ AvailabilityZone: "test-availability-zone", CreatedAt: testTime, Description: testDescr, ID: testId, Name: testName, Os_Vol_Host_Attr_Host: "test-host", Os_Vol_Tenant_Attr_TenantID: testId, Size: 1, SnapshotID: testId, SourceVolid: testId, Status: testStatus, VolumeType: "test-volume-type", }} respBody, err := json.Marshal(&GetVolumesDetailResults{Volumes: resp}) c.Assert(err, gc.IsNil) w.(*responseWriter).Response.StatusCode = 200 w.(*responseWriter).Body = ioutil.NopCloser(bytes.NewBuffer(respBody)) }) resp, err := s.client.GetVolumesDetail() c.Assert(numCalls, gc.Equals, 1) c.Assert(err, gc.IsNil) c.Assert(resp.Volumes, gc.HasLen, 1) volume := resp.Volumes[0] c.Check(volume.AvailabilityZone, gc.Equals, "test-availability-zone") c.Check(volume.CreatedAt, gc.Equals, testTime) c.Check(volume.Description, gc.Equals, testDescr) c.Check(volume.ID, gc.Equals, testId) c.Check(volume.Name, gc.Equals, testName) c.Check(volume.Os_Vol_Host_Attr_Host, gc.Equals, "test-host") c.Check(volume.Os_Vol_Tenant_Attr_TenantID, gc.Equals, testId) c.Check(volume.Size, gc.Equals, 1) c.Check(volume.SnapshotID, gc.Equals, testId) c.Check(volume.SourceVolid, gc.Equals, testId) c.Check(volume.Status, gc.Equals, testStatus) c.Check(volume.VolumeType, gc.Equals, "test-volume-type") } func (s *CinderTestSuite) TestGetVolumesSimple(c *gc.C) { numCalls := 0 s.HandleFunc("/v2/"+testId+"/volumes", func(w http.ResponseWriter, req *http.Request) { numCalls++ resp := []Volume{{ ID: testId, Name: testName, }} respBody, err := json.Marshal(&GetVolumesSimpleResults{Volumes: resp}) c.Assert(err, gc.IsNil) w.(*responseWriter).Response.StatusCode = 200 w.(*responseWriter).Body = ioutil.NopCloser(bytes.NewBuffer(respBody)) }) resp, err := s.client.GetVolumesSimple() c.Assert(numCalls, gc.Equals, 1) c.Assert(err, gc.IsNil) c.Assert(resp.Volumes, gc.HasLen, 1) volume := resp.Volumes[0] c.Check(volume.ID, gc.Equals, testId) c.Check(volume.Name, gc.Equals, testName) } func (s *CinderTestSuite) TestCreateVolumeType(c *gc.C) { origReq := CreateVolumeTypeVolumeTypeParams{ Name: "test-volume-type", ExtraSpecs: CreateVolumeTypeVolumeTypeExtraSpecsParams{ Capabilities: "gpu", }, } numCalls := 0 s.HandleFunc("/v2/"+testId+"/types", func(w http.ResponseWriter, req *http.Request) { numCalls++ reqBody, err := ioutil.ReadAll(req.Body) c.Assert(err, gc.IsNil) var receivedReq CreateVolumeTypeParams err = json.Unmarshal(reqBody, &receivedReq) c.Assert(err, gc.IsNil) c.Check(receivedReq.VolumeType, gc.DeepEquals, origReq) resp := struct { ExtraSpecs struct { Capabilities string `json:"capabilities"` } `json:"extra_specs"` ID string `json:"id"` Name string `json:"name"` }{ ID: "test-id", ExtraSpecs: struct { Capabilities string `json:"capabilities"` }{ Capabilities: receivedReq.VolumeType.ExtraSpecs.Capabilities, }, Name: receivedReq.VolumeType.Name, } respBody, err := json.Marshal(&CreateVolumeTypeResults{VolumeType: resp}) c.Assert(err, gc.IsNil) w.(*responseWriter).StatusCode = 200 w.(*responseWriter).Body = ioutil.NopCloser(bytes.NewBuffer(respBody)) }) resp, err := s.client.CreateVolumeType(origReq) c.Assert(err, gc.IsNil) c.Assert(resp.VolumeType.ID, gc.Not(gc.HasLen), 0) c.Check(resp.VolumeType.ExtraSpecs.Capabilities, gc.Equals, origReq.ExtraSpecs.Capabilities) c.Check(resp.VolumeType.Name, gc.Equals, origReq.Name) } func (s *CinderTestSuite) TestDeleteVolumeType(c *gc.C) { numCalls := 0 s.HandleFunc("/v2/"+testId+"/types/", func(w http.ResponseWriter, req *http.Request) { numCalls++ w.(*responseWriter).StatusCode = 202 w.(*responseWriter).Body = ioutil.NopCloser(bytes.NewBuffer([]byte{})) }) err := s.client.DeleteVolumeType(testId) c.Assert(numCalls, gc.Equals, 1) c.Assert(err, gc.IsNil) } func (s *CinderTestSuite) TestGetVolumeType(c *gc.C) { numCalls := 0 s.HandleFunc("/v2/"+testId+"/types/"+testId, func(w http.ResponseWriter, req *http.Request) { numCalls++ resp := struct { ExtraSpecs struct { Capabilities string `json:"capabilities"` } `json:"extra_specs"` ID string `json:"id"` Name string `json:"name"` }{ ID: testId, ExtraSpecs: struct { Capabilities string `json:"capabilities"` }{ Capabilities: "test-capability", }, Name: testName, } respBody, err := json.Marshal(&GetVolumeTypeResults{VolumeType: resp}) c.Assert(err, gc.IsNil) w.(*responseWriter).StatusCode = 200 w.(*responseWriter).Body = ioutil.NopCloser(bytes.NewBuffer(respBody)) }) resp, err := s.client.GetVolumeType(testId) c.Assert(numCalls, gc.Equals, 1) c.Assert(err, gc.IsNil) c.Assert(resp.VolumeType.ID, gc.Not(gc.HasLen), 0) c.Check(resp.VolumeType.ExtraSpecs.Capabilities, gc.Equals, "test-capability") c.Check(resp.VolumeType.Name, gc.Equals, testName) } func (s *CinderTestSuite) TestGetVolumeTypes(c *gc.C) { numCalls := 0 s.HandleFunc("/v2/"+testId+"/types", func(w http.ResponseWriter, req *http.Request) { numCalls++ resp := []VolumeType{{ ID: testId, ExtraSpecs: struct { Capabilities string `json:"capabilities"` }{ Capabilities: "test-capability", }, Name: testName, }} respBody, err := json.Marshal(&GetVolumeTypesResults{VolumeTypes: resp}) c.Assert(err, gc.IsNil) w.(*responseWriter).StatusCode = 200 w.(*responseWriter).Body = ioutil.NopCloser(bytes.NewBuffer(respBody)) }) resp, err := s.client.GetVolumeTypes() c.Assert(numCalls, gc.Equals, 1) c.Assert(err, gc.IsNil) c.Assert(resp.VolumeTypes, gc.HasLen, 1) volumeType := resp.VolumeTypes[0] c.Check(volumeType.ExtraSpecs.Capabilities, gc.Equals, "test-capability") c.Check(volumeType.Name, gc.Equals, testName) c.Check(volumeType.ID, gc.Equals, testId) } func (s *CinderTestSuite) localDo(req *http.Request) (*http.Response, error) { handler, matchedPattern := s.Handler(req) if matchedPattern == "" { return nil, fmt.Errorf("no test handler registered for %s", req.URL.Path) } fmt.Println(matchedPattern) var response http.Response handler.ServeHTTP(&responseWriter{&response}, req) return &response, nil } type responseWriter struct { *http.Response } func (w *responseWriter) Header() http.Header { return w.Response.Header } func (w *responseWriter) Write(data []byte) (int, error) { return len(data), w.Response.Write(bytes.NewBuffer(data)) } func (w *responseWriter) WriteHeader(statusCode int) { w.Response.StatusCode = statusCode } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/cinder/live_test.go000066400000000000000000000117361414605405700237150ustar00rootroot00000000000000// Copyright 2015 Canonical Ltd. // Licensed under the LGPLv3, see LICENCE file for details. package cinder import ( "log" "net/http" "net/url" "time" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/client" "gopkg.in/goose.v1/identity" ) var _ = gc.Suite(&liveCinderSuite{}) type liveCinderSuite struct { client *Client } func (s *liveCinderSuite) SetUpSuite(c *gc.C) { if *live == false { return } cred, err := identity.CompleteCredentialsFromEnv() if err != nil { log.Fatalf("error retrieving credentials from the environment: %v", err) } authClient := client.NewClient(cred, identity.AuthUserPass, nil) if err = authClient.Authenticate(); err != nil { log.Fatalf("error authenticating: %v", err) } endpoint := authClient.EndpointsForRegion(cred.Region)["volume"] endpointUrl, err := url.Parse(endpoint) if err != nil { log.Fatalf("error parsing endpoint: %v", err) } handleRequest := SetAuthHeaderFn(authClient.Token, func(req *http.Request) (*http.Response, error) { log.Printf("Method: %v", req.Method) log.Printf("URL: %v", req.URL) log.Printf("req.Headers: %v", req.Header) log.Printf("req.Body: %d", req.ContentLength) return http.DefaultClient.Do(req) }) s.client = NewClient(authClient.TenantId(), endpointUrl, handleRequest) } func (s *liveCinderSuite) SetUpTest(c *gc.C) { if *live == false { c.Skip("Not running live tests.") } } func (s *liveCinderSuite) TestVolumesAndSnapshots(c *gc.C) { volInfo, err := s.client.CreateVolume(CreateVolumeVolumeParams{Size: 1}) c.Assert(err, gc.IsNil) defer func() { err := s.client.DeleteVolume(volInfo.Volume.ID) c.Assert(err, gc.IsNil) }() err = <-s.client.VolumeStatusNotifier(volInfo.Volume.ID, "available", 10, 1*time.Second) c.Assert(err, gc.IsNil) snapInfo, err := s.client.CreateSnapshot(CreateSnapshotSnapshotParams{VolumeId: volInfo.Volume.ID}) c.Assert(err, gc.IsNil) c.Check(snapInfo.Snapshot.VolumeID, gc.Equals, volInfo.Volume.ID) knownSnapInfo, err := s.client.GetSnapshot(snapInfo.Snapshot.ID) c.Assert(err, gc.IsNil) c.Check(knownSnapInfo.Snapshot.ID, gc.Equals, snapInfo.Snapshot.ID) // Wait for snapshot to be available (or error?) before deleting. err = <-s.client.SnapshotStatusNotifier(snapInfo.Snapshot.ID, "available", 10, 1*time.Second) c.Check(err, gc.IsNil) err = s.client.DeleteSnapshot(snapInfo.Snapshot.ID) c.Assert(err, gc.IsNil) // Wait for the snapshot to be deleted so that the volume can be deleted. <-s.client.SnapshotStatusNotifier(snapInfo.Snapshot.ID, "deleted", 10, 1*time.Second) } func (s *liveCinderSuite) TestVolumeTypeOperations(c *gc.C) { typeInfo, err := s.client.CreateVolumeType(CreateVolumeTypeVolumeTypeParams{ Name: "number-monster", ExtraSpecs: CreateVolumeTypeVolumeTypeExtraSpecsParams{ Capabilities: "gpu", }, }) c.Assert(err, gc.IsNil) knownTypeInfo, err := s.client.GetVolumeType(typeInfo.VolumeType.ID) c.Assert(err, gc.IsNil) c.Check(knownTypeInfo.VolumeType.ID, gc.Equals, typeInfo.VolumeType.ID) err = s.client.DeleteVolumeType(typeInfo.VolumeType.ID) c.Assert(err, gc.IsNil) } func (s *liveCinderSuite) TestVolumeMetadata(c *gc.C) { metadata := map[string]string{ "a": "b", "c": "d", } volInfo, err := s.client.CreateVolume(CreateVolumeVolumeParams{ Size: 1, Metadata: metadata, }) c.Assert(err, gc.IsNil) defer func() { err := s.client.DeleteVolume(volInfo.Volume.ID) c.Assert(err, gc.IsNil) }() err = <-s.client.VolumeStatusNotifier(volInfo.Volume.ID, "available", 10, 1*time.Second) c.Assert(err, gc.IsNil) result, err := s.client.GetVolume(volInfo.Volume.ID) c.Assert(err, gc.IsNil) c.Assert(result, gc.NotNil) c.Assert(result.Volume.Metadata, gc.DeepEquals, metadata) } func (s *liveCinderSuite) TestListVersions(c *gc.C) { result, err := s.client.ListVersions() c.Assert(err, gc.IsNil) c.Assert(result, gc.NotNil) c.Logf("versions: %#v", result.Versions) c.Assert(len(result.Versions), gc.Not(gc.Equals), 0) } func (s *liveCinderSuite) TestUpdateVolumeMetadata(c *gc.C) { metadata := map[string]string{ "Fresh": "Born", "Twin": "Killers", } volInfo, err := s.client.CreateVolume(CreateVolumeVolumeParams{ Size: 75, Metadata: metadata, }) c.Assert(err, gc.IsNil) volumeId := volInfo.Volume.ID defer func() { err := s.client.DeleteVolume(volumeId) c.Assert(err, gc.IsNil) }() err = <-s.client.VolumeStatusNotifier(volumeId, "available", 10, 1*time.Second) c.Assert(err, gc.IsNil) result, err := s.client.SetVolumeMetadata(volumeId, map[string]string{ "Twin": "Huggers", "Paradise": "People", }) c.Assert(err, gc.IsNil) c.Assert(result["Fresh"], gc.Equals, "Born") c.Assert(result["Twin"], gc.Equals, "Huggers") c.Assert(result["Paradise"], gc.Equals, "People") volResult, err := s.client.GetVolume(volumeId) c.Assert(err, gc.IsNil) c.Assert(volResult, gc.NotNil) c.Assert(volResult.Volume.Metadata["Fresh"], gc.Equals, "Born") c.Assert(volResult.Volume.Metadata["Twin"], gc.Equals, "Huggers") c.Assert(volResult.Volume.Metadata["Paradise"], gc.Equals, "People") } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/cinder/package_test.go000066400000000000000000000004731414605405700243450ustar00rootroot00000000000000// Copyright 2015 Canonical Ltd. // Licensed under the LGPLv3, see LICENCE file for details. package cinder import ( "flag" "testing" gc "gopkg.in/check.v1" ) var ( live = flag.Bool("live", false, "Include live OpenStack tests") ) func init() { flag.Parse() } func Test(t *testing.T) { gc.TestingT(t) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/client/000077500000000000000000000000001414605405700213725ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/client/apiversion.go000066400000000000000000000152021414605405700241000ustar00rootroot00000000000000package client import ( "encoding/json" "fmt" "net/http" "net/url" "path" "strconv" "strings" gooseerrors "gopkg.in/goose.v1/errors" goosehttp "gopkg.in/goose.v1/http" "gopkg.in/goose.v1/logging" ) type apiVersion struct { major int minor int } type apiVersionInfo struct { Version apiVersion `json:"id"` Links []apiVersionLink `json:"links"` Status string `json:"status"` } type apiVersionLink struct { Href string `json:"href"` Rel string `json:"rel"` } type apiURLVersion struct { rootURL url.URL serviceURLSuffix string versions []apiVersionInfo } // getAPIVersionURL returns a full formed serviceURL based on the API version requested, // the rootURL and the serviceURLSuffix. If there is no match to the requested API // version an error is returned. If only the major number is defined for the requested // version, the first match found is returned. func getAPIVersionURL(apiURLVersionInfo *apiURLVersion, requested apiVersion) (string, error) { var match string for _, v := range apiURLVersionInfo.versions { if v.Version.major != requested.major { continue } if requested.minor != -1 && v.Version.minor != requested.minor { continue } for _, link := range v.Links { if link.Rel != "self" { continue } hrefURL, err := url.Parse(link.Href) if err != nil { return "", err } match = hrefURL.Path } if requested.minor != -1 { break } } if match == "" { return "", fmt.Errorf("could not find matching URL") } versionURL := apiURLVersionInfo.rootURL versionURL.Path = path.Join(versionURL.Path, match, apiURLVersionInfo.serviceURLSuffix) return versionURL.String(), nil } func (v *apiVersion) UnmarshalJSON(b []byte) error { var s string if err := json.Unmarshal(b, &s); err != nil { return err } parsed, err := parseVersion(s) if err != nil { return err } *v = parsed return nil } // parseVersion takes a version string into the major and minor ints for an apiVersion // structure. The string part of the data is returned by a request to List API versions // send to an OpenStack service. It is in the format "v.". If apiVersion // is empty, return {-1, -1}, to differentiate with "v0". func parseVersion(s string) (apiVersion, error) { if s == "" { return apiVersion{-1, -1}, nil } s = strings.TrimPrefix(s, "v") parts := strings.SplitN(s, ".", 2) if len(parts) == 0 || len(parts) > 2 { return apiVersion{}, fmt.Errorf("invalid API version %q", s) } var minor int = -1 major, err := strconv.Atoi(parts[0]) if err != nil { return apiVersion{}, err } if len(parts) == 2 { var err error minor, err = strconv.Atoi(parts[1]) if err != nil { return apiVersion{}, err } } return apiVersion{major, minor}, nil } func unmarshallVersion(Versions json.RawMessage) ([]apiVersionInfo, error) { // Some services respond with {"versions":[...]}, and // some respond with {"versions":{"values":[...]}}. var object interface{} var versions []apiVersionInfo if err := json.Unmarshal(Versions, &object); err != nil { return versions, err } if _, ok := object.(map[string]interface{}); ok { var valuesObject struct { Values []apiVersionInfo `json:"values"` } if err := json.Unmarshal(Versions, &valuesObject); err != nil { return versions, err } versions = valuesObject.Values } else { if err := json.Unmarshal(Versions, &versions); err != nil { return versions, err } } return versions, nil } // getAPIVersions returns data on the API versions supported by the specified // service endpoint. Some OpenStack clouds do not support the version endpoint, // in which case this method will return an empty set of versions in the result // structure. func (c *authenticatingClient) getAPIVersions(serviceCatalogURL string) (*apiURLVersion, error) { url, err := url.Parse(serviceCatalogURL) if err != nil { return nil, err } // Identify the version in the URL, if there is one, and record // everything proceeding it. We will need to append this to the // API version-specific base URL. var pathParts, origPathParts []string if url.Path != "/" { // The rackspace object-store endpoint triggers the version removal here, // so keep the original parts for object-store and container endpoints. // e.g. https://storage101.dfw1.clouddrive.com/v1/MossoCloudFS_1019383 origPathParts = strings.Split(strings.Trim(url.Path, "/"), "/") pathParts = origPathParts if _, err := parseVersion(pathParts[0]); err == nil { pathParts = pathParts[1:] } url.Path = "/" } baseURL := url.String() // If this is an object-store serviceType, or an object-store container endpoint, // there is no list version API call to make. Return a apiURLVersion which will // satisfy a requested api version of "", "v1" or "v1.0" if c.serviceURLs["object-store"] != "" && strings.Contains(serviceCatalogURL, c.serviceURLs["object-store"]) { objectStoreLink := apiVersionLink{Href: baseURL, Rel: "self"} objectStoreApiVersionInfo := []apiVersionInfo{ { Version: apiVersion{major: 1, minor: 0}, Links: []apiVersionLink{objectStoreLink}, Status: "stable", }, { Version: apiVersion{major: -1, minor: -1}, Links: []apiVersionLink{objectStoreLink}, Status: "stable", }, } return &apiURLVersion{*url, strings.Join(origPathParts, "/"), objectStoreApiVersionInfo}, nil } // Make sure we haven't already received the version info. c.apiVersionMu.Lock() defer c.apiVersionMu.Unlock() if apiInfo, ok := c.apiURLVersions[baseURL]; ok { return apiInfo, nil } logger := logging.FromCompat(c.logger) logger.Debugf("performing API version discovery for %q", baseURL) var raw struct { Versions json.RawMessage `json:"versions"` } requestData := &goosehttp.RequestData{ RespValue: &raw, ExpectedStatus: []int{ http.StatusOK, http.StatusMultipleChoices, }, } apiURLVersionInfo := &apiURLVersion{ rootURL: *url, serviceURLSuffix: strings.Join(pathParts, "/"), } if err := c.sendRequest("GET", baseURL, c.Token(), requestData); err != nil { if gooseerrors.IsNotFound(err) { // Some OpenStack clouds do not support the // version endpoint, and will return a status // code of 404. We check for 404 and return an // empty set of versions. logger.Warningf("API version discovery failed: %v", err) c.apiURLVersions[baseURL] = apiURLVersionInfo return apiURLVersionInfo, nil } return nil, err } versions, err := unmarshallVersion(raw.Versions) if err != nil { return nil, err } apiURLVersionInfo.versions = versions logger.Debugf("discovered API versions: %+v", versions) // Cache the result. c.apiURLVersions[baseURL] = apiURLVersionInfo return apiURLVersionInfo, nil } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/client/apiversion_test.go000066400000000000000000000166711414605405700251520ustar00rootroot00000000000000package client_test import ( "fmt" "net/http" "strconv" gc "gopkg.in/check.v1" ) type versionHandler struct { authBody string port string } type makeServiceURLTest struct { serviceType string version string parts []string success bool URL string err string } func (s *localLiveSuite) makeServiceURLTests() []makeServiceURLTest { return []makeServiceURLTest{ { // As a special case, if no version is specified // then we use whatever URL is recoded in the // service catalogue verbatim. serviceType: "compute", version: "", parts: []string{}, success: true, URL: "http://localhost:%s", }, { serviceType: "compute", version: "v2.1", parts: []string{"foo", "bar/"}, success: true, URL: "http://localhost:%s/v2.1/foo/bar/", }, { serviceType: "compute", version: "v2.0", parts: []string{}, success: true, URL: "http://localhost:%s/v2.0", }, { serviceType: "compute", version: "v2.0", parts: []string{"foo", "bar/"}, success: true, URL: "http://localhost:%s/v2.0/foo/bar/", }, { serviceType: "compute", version: "v2", parts: []string{"foo", "bar/"}, success: true, URL: "http://localhost:%s/v2.1/foo/bar/", }, { serviceType: "object-store", version: "", parts: []string{"foo", "bar"}, success: true, URL: "http://localhost:%s/swift/v1/foo/bar", }, { serviceType: "object-store", version: "q2.0", parts: []string{"foo", "bar/"}, success: false, err: "strconv.ParseInt: parsing \"q2\": invalid syntax", }, { serviceType: "object-store", version: "v1.7", parts: []string{"foo", "bar/"}, success: false, err: "could not find matching URL", }, { serviceType: "juju-container-test", version: "v1", parts: []string{"foo", "bar/"}, success: true, URL: "http://localhost:%s/swift/v1/foo/bar/", }, { serviceType: "juju-container-test", version: "v0", parts: []string{"foo", "bar/"}, success: false, err: "could not find matching URL", }, { serviceType: "juju-container-test", version: "", parts: []string{"foo", "bar/"}, success: true, URL: "http://localhost:%s/swift/v1/foo/bar/", }, { serviceType: "compute", version: "v25.4", parts: []string{}, success: false, err: "could not find matching URL", }, { serviceType: "no-such-service", version: "", parts: []string{}, success: false, err: "no endpoints known for service type: no-such-service", }, } } func (s *localLiveSuite) TestMakeServiceURL(c *gc.C) { port := "3000" cl := s.assertAuthenticationSuccess(c, port) tests := s.makeServiceURLTests() testCount := len(tests) for i, t := range tests { c.Logf("#%d of %d. %s %s %v", i+1, testCount, t.serviceType, t.version, t.parts) URL, err := cl.MakeServiceURL(t.serviceType, t.version, t.parts) if t.success { c.Assert(err, gc.IsNil) c.Assert(URL, gc.Equals, fmt.Sprintf(t.URL, port)) // Run twice to ensure the version caching is working URL, err = cl.MakeServiceURL(t.serviceType, t.version, t.parts) c.Assert(err, gc.IsNil) c.Assert(URL, gc.Equals, fmt.Sprintf(t.URL, port)) } else { c.Assert(err, gc.ErrorMatches, t.err) } } } func (s *localLiveSuite) TestMakeServiceURLAPIVersionDiscoveryDisabled(c *gc.C) { port := "3000" cl := s.assertAuthenticationSuccess(c, port) wasEnabled := cl.SetVersionDiscoveryEnabled(false) c.Assert(wasEnabled, gc.Equals, true) url, err := cl.MakeServiceURL("compute", "v2.1", []string{"foo", "bar/"}) c.Assert(err, gc.IsNil) c.Assert(url, gc.Equals, fmt.Sprintf("http://localhost:%s/foo/bar/", port)) } func (s *localLiveSuite) TestMakeServiceURLNoAPIVersionEndpoint(c *gc.C) { // See https://bugs.launchpad.net/juju/+bug/1638304 // Some OpenStacks don't support version discovery. port := "3005" cl := s.assertAuthenticationSuccess(c, port) url, err := cl.MakeServiceURL("compute", "v2.1", []string{"foo", "bar/"}) c.Assert(err, gc.IsNil) c.Assert(url, gc.Equals, fmt.Sprintf("http://localhost:%s/foo/bar/", port)) } func (s *localLiveSuite) TestMakeServiceURLValues(c *gc.C) { port := "3003" cl := s.assertAuthenticationSuccess(c, port) tests := s.makeServiceURLTests() testCount := len(tests) for i, t := range tests { c.Logf("#%d of %d. %s %s %v", i+1, testCount, t.serviceType, t.version, t.parts) URL, err := cl.MakeServiceURL(t.serviceType, t.version, t.parts) if t.success { c.Assert(err, gc.IsNil) c.Assert(URL, gc.Equals, fmt.Sprintf(t.URL, port)) // Run twice to ensure the version caching is working URL, err = cl.MakeServiceURL(t.serviceType, t.version, t.parts) c.Assert(err, gc.IsNil) c.Assert(URL, gc.Equals, fmt.Sprintf(t.URL, port)) } else { c.Assert(err, gc.ErrorMatches, t.err) } } } const authInformationBody = `{"versions": [` + `{"status": "stable", "updated": "2015-03-30T00:00:00Z", "media-types": [{"base": "application/json", "type": "application/vnd.openstack.identity-v3+json"}], "id": "v3.4", "links": [{"href": "%s/v3/", "rel": "self"}]},` + `{"status": "stable", "updated": "2014-04-17T00:00:00Z", "media-types": [{"base": "application/json", "type": "application/vnd.openstack.identity-v2.0+json"}], "id": "v2.0", "links": [{"href": "%s/v2.0/", "rel": "self"}, {"href": "http://docs.openstack.org/", "type": "text/html", "rel": "describedby"}]},` + `{"status": "stable", "updated": "2015-03-30T00:00:00Z", "media-types": [{"base": "application/json", "type": "application/vnd.openstack.identity-v3+json"}], "id": "v2.1", "links": [{"href": "%s/v2.1/", "rel": "self"}]}` + `]}` const authValuesInformationBody = `{"versions": {"values": [` + `{"status": "stable", "updated": "2015-03-30T00:00:00Z", "media-types": [{"base": "application/json", "type": "application/vnd.openstack.identity-v3+json"}], "id": "v3.4", "links": [{"href": "%s/v3/", "rel": "self"}]},` + `{"status": "stable", "updated": "2014-04-17T00:00:00Z", "media-types": [{"base": "application/json", "type": "application/vnd.openstack.identity-v2.0+json"}], "id": "v2.0", "links": [{"href": "%s/v2.0/", "rel": "self"}, {"href": "http://docs.openstack.org/", "type": "text/html", "rel": "describedby"}]},` + `{"status": "stable", "updated": "2015-03-30T00:00:00Z", "media-types": [{"base": "application/json", "type": "application/vnd.openstack.identity-v3+json"}], "id": "v2.1", "links": [{"href": "%s/v2.1/", "rel": "self"}]}` + `]}}` func (vh *versionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if vh.authBody == "" { body := `{"message":"Api does not exist","request_id":"83A781AE-9A0C-43C7-B405-310A5A94566E"}` w.WriteHeader(http.StatusNotFound) w.Write([]byte(body)) return } body := []byte(fmt.Sprintf(vh.authBody, "http://localhost:"+vh.port, "http://localhost:"+vh.port, "http://localhost:"+vh.port)) // workaround for https://code.google.com/p/go/issues/detail?id=4454 w.Header().Set("Content-Length", strconv.Itoa(len(body))) w.WriteHeader(http.StatusMultipleChoices) w.Write(body) } func startApiVersionMux(vh versionHandler) string { mux := http.NewServeMux() mux.Handle("/", &vh) go http.ListenAndServe(":"+vh.port, mux) return fmt.Sprintf("Listening on localhost:%s...\n", vh.port) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/client/client.go000066400000000000000000000424101414605405700232000ustar00rootroot00000000000000package client import ( "errors" "fmt" "net/url" "sort" "strings" "sync" "time" gooseerrors "gopkg.in/goose.v1/errors" goosehttp "gopkg.in/goose.v1/http" "gopkg.in/goose.v1/identity" "gopkg.in/goose.v1/logging" goosesync "gopkg.in/goose.v1/sync" ) const ( apiTokens = "/tokens" apiTokensV3 = "/auth/tokens" // The HTTP request methods. GET = "GET" POST = "POST" PUT = "PUT" DELETE = "DELETE" HEAD = "HEAD" COPY = "COPY" ) // Client implementations sends service requests to an OpenStack deployment. type Client interface { SendRequest(method, svcType, svcVersion, apiCall string, requestData *goosehttp.RequestData) (err error) // MakeServiceURL prepares a full URL to a service endpoint, with optional // URL parts. MakeServiceURL(serviceType, apiVersion string, parts []string) (string, error) } // AuthenticatingClient sends service requests to an OpenStack deployment after first validating // a user's credentials. type AuthenticatingClient interface { Client // SetVersionDiscoveryEnabled enables or disables API version // discovery. Discovery is enabled by default. If enabled, // the client will attempt to list all versions available // for a service and use the best match. Otherwise, any API // version specified in an SendRequest or MakeServiceURL // call will be ignored, and the service catalogue endpoint // URL will be used directly. SetVersionDiscoveryEnabled(bool) bool // SetRequiredServiceTypes sets the service types that the // openstack must provide. SetRequiredServiceTypes(requiredServiceTypes []string) // Authenticate authenticates the client with the OpenStack // identity service. Authenticate() error // IsAuthenticated reports whether the client is // authenticated. IsAuthenticated() bool // Token returns the authentication token. If the client // is not yet authenticated, this will return an empty // string. Token() string // UserId returns the user ID for authentication. UserId() string // TenantId returns the tenant ID for authentication. TenantId() string // EndpointsForRegion returns the service catalog URLs // for the specified region. EndpointsForRegion(region string) identity.ServiceURLs // IdentityAuthOptions returns a list of valid auth options // for the given openstack or error if fetching fails. IdentityAuthOptions() (identity.AuthOptions, error) } // A single http client is shared between all Goose clients. var sharedHttpClient = goosehttp.New() // This client sends requests without authenticating. type client struct { mu sync.Mutex logger logging.CompatLogger baseURL string httpClient *goosehttp.Client } var _ Client = (*client)(nil) // This client authenticates before sending requests. type authenticatingClient struct { client creds *identity.Credentials authMode identity.Authenticator auth AuthenticatingClient authOptions identity.AuthOptions // The service types which must be available after authentication, // or else services which use this client will not be able to function as expected. requiredServiceTypes []string tokenId string tenantId string userId string // Service type to endpoint URLs for each available region regionServiceURLs map[string]identity.ServiceURLs // Service type to endpoint URLs for the authenticated region serviceURLs identity.ServiceURLs // API versions available based on service catalogue URL. apiVersionMu sync.Mutex apiVersionDiscoveryEnabled bool apiURLVersions map[string]*apiURLVersion } func (c *authenticatingClient) EndpointsForRegion(region string) identity.ServiceURLs { return c.regionServiceURLs[region] } var _ AuthenticatingClient = (*authenticatingClient)(nil) func NewPublicClient(baseURL string, logger logging.CompatLogger) Client { client := client{baseURL: baseURL, logger: logger, httpClient: sharedHttpClient} return &client } func NewNonValidatingPublicClient(baseURL string, logger logging.CompatLogger) Client { return &client{ baseURL: baseURL, logger: logger, httpClient: goosehttp.NewNonSSLValidating(), } } var defaultRequiredServiceTypes = []string{"compute", "object-store"} func newClient(creds *identity.Credentials, auth_method identity.AuthMode, httpClient *goosehttp.Client, logger logging.CompatLogger) AuthenticatingClient { client_creds := *creds if strings.HasSuffix(client_creds.URL, "/") { client_creds.URL = client_creds.URL[:len(client_creds.URL)-1] } switch auth_method { case identity.AuthUserPassV3: client_creds.URL = client_creds.URL + apiTokensV3 default: client_creds.URL = client_creds.URL + apiTokens } client := authenticatingClient{ creds: &client_creds, requiredServiceTypes: defaultRequiredServiceTypes, client: client{logger: logger, httpClient: httpClient}, apiVersionDiscoveryEnabled: true, } client.auth = &client client.authMode = identity.NewAuthenticator(auth_method, httpClient) return &client } func NewClient(creds *identity.Credentials, auth_method identity.AuthMode, logger logging.CompatLogger) AuthenticatingClient { return newClient(creds, auth_method, sharedHttpClient, logger) } func NewNonValidatingClient(creds *identity.Credentials, auth_method identity.AuthMode, logger logging.CompatLogger) AuthenticatingClient { return newClient(creds, auth_method, goosehttp.NewNonSSLValidating(), logger) } func (c *client) sendRequest(method, url, token string, requestData *goosehttp.RequestData) (err error) { if requestData.ReqValue != nil || requestData.RespValue != nil { err = c.httpClient.JsonRequest(method, url, token, requestData, c.logger) } else { err = c.httpClient.BinaryRequest(method, url, token, requestData, c.logger) } return } func (c *client) SendRequest(method, svcType, apiVersion, apiCall string, requestData *goosehttp.RequestData) error { url, _ := c.MakeServiceURL(svcType, apiVersion, []string{apiCall}) return c.sendRequest(method, url, "", requestData) } func makeURL(base string, parts []string) string { if !strings.HasSuffix(base, "/") && len(parts) > 0 { base += "/" } return base + strings.Join(parts, "/") } func (c *client) MakeServiceURL(serviceType, apiVersion string, parts []string) (string, error) { return makeURL(c.baseURL, parts), nil } func (c *authenticatingClient) SetRequiredServiceTypes(requiredServiceTypes []string) { c.requiredServiceTypes = requiredServiceTypes } func (c *authenticatingClient) SendRequest( method, svcType, apiVersion, apiCall string, requestData *goosehttp.RequestData, ) (err error) { err = c.sendAuthRequest(method, svcType, apiVersion, apiCall, requestData) if gooseerrors.IsUnauthorised(err) { c.setToken("") err = c.sendAuthRequest(method, svcType, apiVersion, apiCall, requestData) } return } func (c *authenticatingClient) sendAuthRequest( method, svcType, apiVersion, apiCall string, requestData *goosehttp.RequestData, ) (err error) { if err = c.Authenticate(); err != nil { return } url, err := c.MakeServiceURL(svcType, apiVersion, []string{apiCall}) if err != nil { return } return c.sendRequest(method, url, c.Token(), requestData) } // MakeServiceURL uses an endpoint matching the apiVersion for the given service type. // Given a major version only, the version with the highest minor will be used. // // object-store and container service types have no versions. For these services, the // caller may pass "" for apiVersion, to use the service catalogue URL without any // version discovery. func (c *authenticatingClient) MakeServiceURL(serviceType, apiVersion string, parts []string) (string, error) { if !c.IsAuthenticated() { return "", errors.New("cannot get endpoint URL without being authenticated") } serviceURL, ok := c.serviceURLs[serviceType] if !ok { return "", errors.New("no endpoints known for service type: " + serviceType) } if apiVersion == "" || !c.isAPIVersionDiscoveryEnabled() { return makeURL(serviceURL, parts), nil } requestedVersion, err := parseVersion(apiVersion) if err != nil { return "", err } apiURLVersionInfo, err := c.getAPIVersions(serviceURL) if err != nil { return "", err } if len(apiURLVersionInfo.versions) == 0 { // There is no API version information for this service, // so just use the service URL as if discovery were // disabled. This isn't guaranteed to result in a valid // endpoint, but it's the best we can do. logger := logging.FromCompat(c.logger) logger.Warningf("falling back to catalogue service URL") return makeURL(serviceURL, parts), nil } serviceURL, err = getAPIVersionURL(apiURLVersionInfo, requestedVersion) if err != nil { return "", err } return makeURL(serviceURL, parts), nil } func (c *authenticatingClient) SetVersionDiscoveryEnabled(enabled bool) bool { c.apiVersionMu.Lock() defer c.apiVersionMu.Unlock() old := c.apiVersionDiscoveryEnabled c.apiVersionDiscoveryEnabled = enabled return old } func (c *authenticatingClient) isAPIVersionDiscoveryEnabled() bool { c.apiVersionMu.Lock() defer c.apiVersionMu.Unlock() return c.apiVersionDiscoveryEnabled } // Return the relevant service endpoint URLs for this client's region. // The region comes from the client credentials. func (c *authenticatingClient) createServiceURLs() error { var serviceURLs identity.ServiceURLs = nil var otherServiceTypeRegions map[string][]string = make(map[string][]string) for region, urls := range c.regionServiceURLs { if regionMatches(c.creds.Region, region) { if serviceURLs == nil { serviceURLs = make(identity.ServiceURLs) } for serviceType, endpointURL := range urls { serviceURLs[serviceType] = endpointURL } } else { for serviceType := range urls { regions := otherServiceTypeRegions[serviceType] if regions == nil { regions = []string{} } otherServiceTypeRegions[serviceType] = append(regions, region) } } } var errorPrefix string var possibleRegions, missingServiceTypes []string if serviceURLs == nil { var knownRegions []string for r := range c.regionServiceURLs { knownRegions = append(knownRegions, r) } missingServiceTypes, possibleRegions = c.possibleRegions([]string{}) errorPrefix = fmt.Sprintf("invalid region %q", c.creds.Region) } else { existingServiceTypes := []string{} for serviceType := range serviceURLs { if containsString(c.requiredServiceTypes, serviceType) { existingServiceTypes = append(existingServiceTypes, serviceType) } } missingServiceTypes, possibleRegions = c.possibleRegions(existingServiceTypes) errorPrefix = fmt.Sprintf("the configured region %q does not allow access to all required services, namely: %s\n"+ "access to these services is missing: %s", c.creds.Region, strings.Join(c.requiredServiceTypes, ", "), strings.Join(missingServiceTypes, ", ")) } if len(missingServiceTypes) > 0 { if len(possibleRegions) > 0 { return fmt.Errorf("%s\none of these regions may be suitable instead: %s", errorPrefix, strings.Join(possibleRegions, ", ")) } else { return errors.New(errorPrefix) } } c.serviceURLs = serviceURLs return nil } // possibleRegions returns a list of regions, any of which will allow the client to access the required service types. // This method is called when a client authenticates and the configured region does not allow access to all the // required service types. The service types which are accessible, accessibleServiceTypes, is passed in and the // method returns what the missing service types are as well as valid regions. func (c *authenticatingClient) possibleRegions(accessibleServiceTypes []string) (missingServiceTypes []string, possibleRegions []string) { var serviceTypeRegions map[string][]string // Figure out the missing service types and build up a map of all service types to regions // obtained from the authentication response. for _, serviceType := range c.requiredServiceTypes { if !containsString(accessibleServiceTypes, serviceType) { missingServiceTypes = append(missingServiceTypes, serviceType) serviceTypeRegions = c.extractServiceTypeRegions() } } // Look at the region lists for each missing service type and determine which subset of those could // be used to allow access to all required service types. The most specific regions are used. if len(missingServiceTypes) == 1 { possibleRegions = serviceTypeRegions[missingServiceTypes[0]] } else { for _, serviceType := range missingServiceTypes { for _, serviceTypeCompare := range missingServiceTypes { if serviceType == serviceTypeCompare { continue } possibleRegions = appendPossibleRegions(serviceType, serviceTypeCompare, serviceTypeRegions, possibleRegions) } } } sort.Strings(possibleRegions) return } // utility function to extract map of service types -> region func (c *authenticatingClient) extractServiceTypeRegions() map[string][]string { serviceTypeRegions := make(map[string][]string) for region, serviceURLs := range c.regionServiceURLs { for regionServiceType := range serviceURLs { regions := serviceTypeRegions[regionServiceType] if !containsString(regions, region) { serviceTypeRegions[regionServiceType] = append(regions, region) } } } return serviceTypeRegions } // extract the common regions for each service type and append them to the possible regions slice. func appendPossibleRegions(serviceType, serviceTypeCompare string, serviceTypeRegions map[string][]string, possibleRegions []string) []string { regions := serviceTypeRegions[serviceType] for _, region := range regions { regionsCompare := serviceTypeRegions[serviceTypeCompare] if !containsBaseRegion(regionsCompare, region) && containsSuperRegion(regionsCompare, region) { possibleRegions = append(possibleRegions, region) } } return possibleRegions } // utility function to see if element exists in values slice. func containsString(values []string, element string) bool { for _, value := range values { if value == element { return true } } return false } // containsBaseRegion returns true if any of the regions in values are based on region. // see client.regionMatches. func containsBaseRegion(values []string, region string) bool { for _, value := range values { if regionMatches(value, region) && region != value { return true } } return false } // containsSuperRegion returns true if region is based on any of the regions in values. // see client.regionMatches. func containsSuperRegion(values []string, region string) bool { for _, value := range values { if regionMatches(region, value) || region == value { return true } } return false } func regionMatches(userRegion, endpointRegion string) bool { // The user specified region (from the credentials config) matches // the endpoint region if the user region equals or ends with the endpoint region. // eg user region "az-1.region-a.geo-1" matches endpoint region "region-a.geo-1" return strings.HasSuffix(userRegion, endpointRegion) } func (c *authenticatingClient) setToken(tokenId string) { c.mu.Lock() c.tokenId = tokenId c.mu.Unlock() } func (c *authenticatingClient) Token() string { c.mu.Lock() defer c.mu.Unlock() return c.tokenId } func (c *authenticatingClient) UserId() string { return c.userId } func (c *authenticatingClient) TenantId() string { return c.tenantId } func (c *authenticatingClient) IsAuthenticated() bool { c.mu.Lock() defer c.mu.Unlock() return c.tokenId != "" } var authenticationTimeout = time.Duration(60) * time.Second func (c *authenticatingClient) Authenticate() (err error) { ok := goosesync.RunWithTimeout(authenticationTimeout, func() { err = c.doAuthenticate() }) if !ok { err = gooseerrors.NewTimeoutf( nil, "", "Authentication response not received in %s.", authenticationTimeout) } return err } func (c *authenticatingClient) doAuthenticate() error { c.mu.Lock() defer c.mu.Unlock() if c.creds == nil || c.tokenId != "" { return nil } if c.authMode == nil { return fmt.Errorf("Authentication method has not been specified") } var ( authDetails *identity.AuthDetails err error ) if authDetails, err = c.authMode.Auth(c.creds); err != nil { return gooseerrors.Newf(err, "authentication failed") } logger := logging.FromCompat(c.logger) logger.Debugf("auth details: %+v", authDetails) c.regionServiceURLs = authDetails.RegionServiceURLs if err := c.createServiceURLs(); err != nil { return gooseerrors.Newf(err, "cannot create service URLs") } c.apiURLVersions = make(map[string]*apiURLVersion) c.tenantId = authDetails.TenantId c.userId = authDetails.UserId // A valid token indicates authorisation has been successful, so it needs to be set last. It must be set // after the service URLs have been extracted. c.tokenId = authDetails.Token return nil } func (c *authenticatingClient) IdentityAuthOptions() (identity.AuthOptions, error) { c.mu.Lock() defer c.mu.Unlock() if len(c.authOptions) > 0 { return c.authOptions, nil } baseURL := c.baseURL if baseURL == "" { parsedURL, err := url.Parse(c.creds.URL) if err != nil { return identity.AuthOptions{}, gooseerrors.Newf(err, "trying to determine auth information url") } // this cannot fail. authInfoPath, _ := url.Parse("/") baseURL = parsedURL.ResolveReference(authInfoPath).String() } authOptions, err := identity.FetchAuthOptions(baseURL, c.httpClient, c.logger) if err != nil { return identity.AuthOptions{}, gooseerrors.Newf(err, "auth options fetching failed") } c.authOptions = authOptions return authOptions, nil } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/client/client_test.go000066400000000000000000000021631414605405700242400ustar00rootroot00000000000000package client_test import ( "flag" "testing" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/identity" ) var live = flag.Bool("live", false, "Include live OpenStack (Canonistack) tests") var liveAuthMode = flag.String( "live-auth-mode", "userpass", "The authentication mode to use when running live tests [all|legacy|userpass|keypair]") func Test(t *testing.T) { var allAuthModes = []identity.AuthMode{identity.AuthLegacy, identity.AuthUserPass, identity.AuthKeyPair} var liveAuthModes []identity.AuthMode switch *liveAuthMode { default: t.Fatalf("Invalid auth method specified: %s", *liveAuthMode) case "all": liveAuthModes = allAuthModes case "": case "keypair": liveAuthModes = []identity.AuthMode{identity.AuthKeyPair} case "userpass": liveAuthModes = []identity.AuthMode{identity.AuthUserPass} case "legacy": liveAuthModes = []identity.AuthMode{identity.AuthLegacy} } if *live { cred, err := identity.CompleteCredentialsFromEnv() if err != nil { t.Fatalf("Error setting up test suite: %v", err) } registerOpenStackTests(cred, liveAuthModes) } registerLocalTests(allAuthModes) gc.TestingT(t) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/client/export_test.go000066400000000000000000000006471414605405700243100ustar00rootroot00000000000000package client import ( "time" "gopkg.in/goose.v1/identity" ) type AuthCleanup func() func SetAuthenticationTimeout(timeout time.Duration) AuthCleanup { origTimeout := authenticationTimeout authenticationTimeout = timeout return func() { authenticationTimeout = origTimeout } } func SetAuthenticator(client AuthenticatingClient, auth identity.Authenticator) { client.(*authenticatingClient).authMode = auth } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/client/live_test.go000066400000000000000000000045451414605405700237270ustar00rootroot00000000000000package client_test import ( gc "gopkg.in/check.v1" "gopkg.in/goose.v1/client" "gopkg.in/goose.v1/identity" ) func registerOpenStackTests(cred *identity.Credentials, authModes []identity.AuthMode) { for _, authMode := range authModes { gc.Suite(&LiveTests{ cred: cred, authMode: authMode, }) } } type LiveTests struct { cred *identity.Credentials authMode identity.AuthMode } func (s *LiveTests) SetUpSuite(c *gc.C) { c.Logf("Running tests with authentication method %v", s.authMode) } func (s *LiveTests) TearDownSuite(c *gc.C) { // noop, called by local test suite. } func (s *LiveTests) SetUpTest(c *gc.C) { // noop, called by local test suite. } func (s *LiveTests) TearDownTest(c *gc.C) { // noop, called by local test suite. } func (s *LiveTests) TestAuthenticateFail(c *gc.C) { cred := *s.cred cred.User = "fred" cred.Secrets = "broken" cred.Region = "" osclient := client.NewClient(&cred, s.authMode, nil) c.Assert(osclient.IsAuthenticated(), gc.Equals, false) err := osclient.Authenticate() c.Assert(err, gc.ErrorMatches, "authentication failed(\n|.)*") } func (s *LiveTests) TestAuthenticate(c *gc.C) { cl := client.NewClient(s.cred, s.authMode, nil) err := cl.Authenticate() c.Assert(err, gc.IsNil) c.Assert(cl.IsAuthenticated(), gc.Equals, true) // Check service endpoints are discovered if s.authMode == identity.AuthLegacy { // AuthLegacy doesn't use the openstack test double, therefore // MakeServiceURL won't work correctly as the endpoint urls are used, // but setup as bad addresses for AuthLegacy c.Log("half of this test will not work with legacy auth") } else { url, err := cl.MakeServiceURL("compute", "v2", nil) c.Check(err, gc.IsNil) c.Check(url, gc.NotNil) url, err = cl.MakeServiceURL("object-store", "", nil) c.Check(err, gc.IsNil) c.Check(url, gc.NotNil) } } func (s *LiveTests) TestAuthDiscover(c *gc.C) { if s.authMode == identity.AuthLegacy { c.Skip("this test will not work with legacy auth") } cl := client.NewClient(s.cred, s.authMode, nil) options, err := cl.IdentityAuthOptions() c.Assert(err, gc.IsNil) optionsAvailable := len(options) > 0 c.Assert(optionsAvailable, gc.Equals, true) for _, option := range options { switch option.Mode { case identity.AuthUserPass, identity.AuthUserPassV3: default: c.Logf("unknown identity AuthMode %v", option) c.FailNow() } } } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/client/local_test.go000066400000000000000000000370411414605405700240570ustar00rootroot00000000000000package client_test import ( "encoding/json" "fmt" "net/url" "runtime" "sync" "time" "github.com/juju/loggo" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/client" "gopkg.in/goose.v1/errors" "gopkg.in/goose.v1/identity" "gopkg.in/goose.v1/logging" "gopkg.in/goose.v1/swift" "gopkg.in/goose.v1/testing/httpsuite" "gopkg.in/goose.v1/testservices" "gopkg.in/goose.v1/testservices/identityservice" "gopkg.in/goose.v1/testservices/openstackservice" ) func registerLocalTests(authModes []identity.AuthMode) { for _, authMode := range authModes { gc.Suite(&localLiveSuite{ LiveTests: LiveTests{ authMode: authMode, }, }) } gc.Suite(&localHTTPSSuite{HTTPSuite: httpsuite.HTTPSuite{UseTLS: true}}) } // localLiveSuite runs tests from LiveTests using a fake // identity server that runs within the test process itself. type localLiveSuite struct { LiveTests // The following attributes are for using testing doubles. httpsuite.HTTPSuite service testservices.HttpService versionHandlers map[string]*versionHandler } func (s *localLiveSuite) SetUpSuite(c *gc.C) { c.Logf("Using identity service test double") s.HTTPSuite.SetUpSuite(c) s.cred = &identity.Credentials{ URL: s.Server.URL, User: "fred", Secrets: "secret", Region: "zone1.some region", TenantName: "tenant", ProjectDomain: "default", } var logMsg []string switch s.authMode { default: panic("Invalid authentication method") case identity.AuthKeyPair: // The openstack test service sets up keypair authentication. s.service, logMsg = openstackservice.New(s.cred, identity.AuthKeyPair, s.UseTLS) // Add an additional endpoint so region filtering can be properly tested. serviceDef := identityservice.Service{ V2: identityservice.V2Service{ Name: "nova", Type: "compute", Endpoints: []identityservice.Endpoint{ {PublicURL: "http://nova2", Region: "zone2.RegionOne"}, }, }} s.service.(*openstackservice.Openstack).Identity.AddService(serviceDef) case identity.AuthUserPass: // The openstack test service sets up userpass authentication. s.service, logMsg = openstackservice.New(s.cred, identity.AuthUserPass, s.UseTLS) // Add an additional endpoint so region filtering can be properly tested. serviceDef := identityservice.Service{ V2: identityservice.V2Service{ Name: "nova", Type: "compute", Endpoints: []identityservice.Endpoint{ {PublicURL: "http://nova2", Region: "zone2.RegionOne"}, }, }} s.service.(*openstackservice.Openstack).Identity.AddService(serviceDef) case identity.AuthUserPassV3: // The openstack test service sets up userpass authentication. s.service, logMsg = openstackservice.New(s.cred, identity.AuthUserPass, s.UseTLS) // Add an additional endpoint so region filtering can be properly tested. serviceDef := identityservice.Service{ V3: identityservice.V3Service{ Name: "nova", Type: "compute", Endpoints: identityservice.NewV3Endpoints("", "", "http://nova2", "zone2.RegionOne"), }} s.service.(*openstackservice.Openstack).Identity.AddService(serviceDef) case identity.AuthLegacy: legacy := identityservice.NewLegacy() legacy.AddUser(s.cred.User, s.cred.Secrets, s.cred.TenantName, "default") legacy.SetManagementURL("http://management.test.invalid/url") s.service = legacy } for _, msg := range logMsg { c.Logf(msg) } if s.authMode != identity.AuthLegacy { s.service.SetupHTTP(nil) } s.versionHandlers = make(map[string]*versionHandler) s.LiveTests.SetUpSuite(c) } func (s *localLiveSuite) TearDownSuite(c *gc.C) { s.LiveTests.TearDownSuite(c) s.HTTPSuite.TearDownSuite(c) s.service.Stop() } func (s *localLiveSuite) SetUpTest(c *gc.C) { s.HTTPSuite.SetUpTest(c) s.LiveTests.SetUpTest(c) if s.authMode == identity.AuthLegacy { s.service.SetupHTTP(s.Mux) } } func (s *localLiveSuite) TearDownTest(c *gc.C) { s.LiveTests.TearDownTest(c) s.HTTPSuite.TearDownTest(c) } // Additional tests to be run against the service double only go here. func (s *localLiveSuite) TestInvalidRegion(c *gc.C) { if s.authMode == identity.AuthLegacy { c.Skip("legacy authentication doesn't use regions") } creds := &identity.Credentials{ User: "fred", URL: s.service.(*openstackservice.Openstack).URLs["identity"], Secrets: "secret", Region: "invalid", } cl := client.NewClient(creds, s.authMode, nil) err := cl.Authenticate() c.Assert(err.Error(), gc.Matches, "(.|\n)*invalid region(.|\n)*") } // Test service lookup with inexact region matching. func (s *localLiveSuite) TestInexactRegionMatch(c *gc.C) { if s.authMode == identity.AuthLegacy { c.Skip("legacy authentication doesn't use regions") } cl := client.NewClient(s.cred, s.authMode, nil) err := cl.Authenticate() serviceURL, err := cl.MakeServiceURL("compute", "v2", []string{}) c.Assert(err, gc.IsNil) _, err = url.Parse(serviceURL) c.Assert(err, gc.IsNil) serviceURL, err = cl.MakeServiceURL("object-store", "", []string{}) c.Assert(err, gc.IsNil) _, err = url.Parse(serviceURL) c.Assert(err, gc.IsNil) } type fakeAuthenticator struct { mu sync.Mutex nrCallers int // authStart is used as a gate to signal the fake authenticator that it can start. authStart chan struct{} port string // for startApiVersionMux() } func newAuthenticator(bufsize int, port string) *fakeAuthenticator { return &fakeAuthenticator{ authStart: make(chan struct{}, bufsize), port: port, } } // doNewAuthenticator sets up the HTTP listener on localhost: for testing // api version functionality within MakeServiceURL if we're using a fakeAuthenticator. func (s *localLiveSuite) doNewAuthenticator(c *gc.C, bufsize int, port string) *fakeAuthenticator { newAuth := newAuthenticator(bufsize, port) newAuth.mu.Lock() if _, ok := s.versionHandlers[port]; !ok { var vh versionHandler switch port { case "3000": vh.authBody = authInformationBody case "3003": vh.authBody = authValuesInformationBody case "3005": vh.authBody = "" } vh.port = port c.Logf(startApiVersionMux(vh)) s.versionHandlers[port] = &vh } newAuth.mu.Unlock() return newAuth } func (auth *fakeAuthenticator) Auth(creds *identity.Credentials) (*identity.AuthDetails, error) { auth.mu.Lock() auth.nrCallers++ auth.mu.Unlock() // Wait till the test says the authenticator can proceed. <-auth.authStart runtime.Gosched() defer func() { auth.mu.Lock() auth.nrCallers-- auth.mu.Unlock() }() auth.mu.Lock() tooManyCallers := auth.nrCallers > 1 auth.mu.Unlock() if tooManyCallers { return nil, fmt.Errorf("Too many callers of Auth function") } URLs := make(map[string]identity.ServiceURLs) endpoints := make(map[string]string) endpoints["compute"] = fmt.Sprintf("http://localhost:%s", auth.port) endpoints["object-store"] = fmt.Sprintf("http://localhost:%s/swift/v1", auth.port) endpoints["juju-container-test"] = fmt.Sprintf("http://localhost:%s/swift/v1", auth.port) URLs[creds.Region] = endpoints return &identity.AuthDetails{ Token: "token", TenantId: "tenant", UserId: "1", RegionServiceURLs: URLs, }, nil } func (s *localLiveSuite) TestAuthenticationTimeout(c *gc.C) { cl := client.NewClient(s.cred, s.authMode, nil) defer client.SetAuthenticationTimeout(1 * time.Millisecond)() auth := s.doNewAuthenticator(c, 0, "3003") client.SetAuthenticator(cl, auth) var err error err = cl.Authenticate() // Wake up the authenticator after we have timed out. auth.authStart <- struct{}{} c.Assert(errors.IsTimeout(err), gc.Equals, true) } func (s *localLiveSuite) assertAuthenticationSuccess(c *gc.C, port string) client.AuthenticatingClient { cl := client.NewClient(s.cred, s.authMode, logging.LoggoLogger{loggo.GetLogger("goose.client")}) cl.SetRequiredServiceTypes([]string{"compute"}) defer client.SetAuthenticationTimeout(2 * time.Millisecond)() auth := s.doNewAuthenticator(c, 1, port) client.SetAuthenticator(cl, auth) // Signal that the authenticator can proceed immediately. auth.authStart <- struct{}{} err := cl.Authenticate() c.Assert(err, gc.IsNil) // It completed with no error but check it also ran correctly. c.Assert(cl.IsAuthenticated(), gc.Equals, true) return cl } func (s *localLiveSuite) TestAuthenticationSuccess(c *gc.C) { cl := s.assertAuthenticationSuccess(c, "3000") URL, err := cl.MakeServiceURL("compute", "v2.0", nil) c.Assert(err, gc.IsNil) c.Assert(URL, gc.Equals, "http://localhost:3000/v2.0") } func checkAuthentication(cl client.AuthenticatingClient) error { err := cl.Authenticate() if err != nil { return err } URL, err := cl.MakeServiceURL("compute", "v3", nil) if err != nil { return err } if URL != "http://localhost:3000/v3" { return fmt.Errorf("Unexpected URL: %s", URL) } return nil } func (s *localLiveSuite) TestAuthenticationForbidsMultipleCallers(c *gc.C) { if s.authMode == identity.AuthLegacy { c.Skip("legacy authentication") } cl := client.NewClient(s.cred, s.authMode, nil) cl.SetRequiredServiceTypes([]string{"compute"}) auth := s.doNewAuthenticator(c, 2, "3000") client.SetAuthenticator(cl, auth) // Signal that the authenticator can proceed immediately. auth.authStart <- struct{}{} auth.authStart <- struct{}{} var allDone sync.WaitGroup allDone.Add(2) var err1, err2 error go func() { err1 = checkAuthentication(cl) allDone.Done() }() go func() { err2 = checkAuthentication(cl) allDone.Done() }() allDone.Wait() c.Assert(err1, gc.IsNil) c.Assert(err2, gc.IsNil) } type configurableAuth struct { regionsURLs map[string]identity.ServiceURLs } func NewConfigurableAuth(regionsURLData string) *configurableAuth { auth := &configurableAuth{} err := json.Unmarshal([]byte(regionsURLData), &auth.regionsURLs) if err != nil { panic(err) } return auth } func (auth *configurableAuth) Auth(creds *identity.Credentials) (*identity.AuthDetails, error) { return &identity.AuthDetails{ Token: "token", TenantId: "tenant", UserId: "1", RegionServiceURLs: auth.regionsURLs, }, nil } type authRegionTest struct { region string regionURLInfo string errorMsg string } var missingEndpointMsgf = "(.|\n)*the configured region %q does not allow access to all required services, namely: %s(.|\n)*access to these services is missing: %s" var missingEndpointSuggestRegionMsgf = "(.|\n)*the configured region %q does not allow access to all required services, namely: %s(.|\n)*access to these services is missing: %s(.|\n)*one of these regions may be suitable instead: %s" var invalidRegionMsgf = "(.|\n)*invalid region %q" var authRegionTests = []authRegionTest{ { "a.region.1", `{"a.region.1":{"compute":"http://foo"}}`, fmt.Sprintf(missingEndpointMsgf, "a.region.1", "compute, object-store", "object-store"), }, { "b.region.1", `{"a.region.1":{"compute":"http://foo"}}`, fmt.Sprintf(invalidRegionMsgf, "b.region.1"), }, { "b.region.1", `{"a.region.1":{"compute":"http://foo"}, "region.1":{"object-store":"http://foobar"}}`, fmt.Sprintf(missingEndpointSuggestRegionMsgf, "b.region.1", "compute, object-store", "compute", "a.region.1"), }, { "region.1", `{"a.region.1":{"compute":"http://foo"}, "region.1":{"object-store":"http://foobar"}}`, fmt.Sprintf(missingEndpointSuggestRegionMsgf, "region.1", "compute, object-store", "compute", "a.region.1"), }, } func (s *localLiveSuite) TestNonAccessibleServiceType(c *gc.C) { if s.authMode == identity.AuthLegacy { c.Skip("legacy authentication") } for _, at := range authRegionTests { s.cred.Region = at.region cl := client.NewClient(s.cred, s.authMode, nil) auth := NewConfigurableAuth(at.regionURLInfo) client.SetAuthenticator(cl, auth) err := cl.Authenticate() c.Assert(err, gc.ErrorMatches, at.errorMsg) } } type localHTTPSSuite struct { // The following attributes are for using testing doubles. httpsuite.HTTPSuite service testservices.HttpService cred *identity.Credentials } func (s *localHTTPSSuite) SetUpSuite(c *gc.C) { c.Logf("Using identity service test double") s.HTTPSuite.SetUpSuite(c) c.Assert(s.Server.URL[:8], gc.Equals, "https://") s.cred = &identity.Credentials{ User: "fred", Secrets: "secret", Region: "zone1.some region", TenantName: "tenant", } // The openstack test service sets up userpass authentication. var logMsg []string s.service, logMsg = openstackservice.New(s.cred, identity.AuthUserPass, s.UseTLS) for _, msg := range logMsg { c.Logf(msg) } // Add an additional endpoint so region filtering can be properly tested. serviceDef := identityservice.Service{ V2: identityservice.V2Service{ Name: "nova", Type: "compute", Endpoints: []identityservice.Endpoint{ {PublicURL: "https://nova2", Region: "zone2.RegionOne"}, }, }} s.service.(*openstackservice.Openstack).Identity.AddService(serviceDef) s.service.SetupHTTP(nil) } func (s *localHTTPSSuite) TearDownSuite(c *gc.C) { s.HTTPSuite.TearDownSuite(c) s.service.Stop() } func (s *localHTTPSSuite) SetUpTest(c *gc.C) { s.HTTPSuite.SetUpTest(c) } func (s *localHTTPSSuite) TearDownTest(c *gc.C) { s.HTTPSuite.TearDownTest(c) } func (s *localHTTPSSuite) TestDefaultClientRefusesSelfSigned(c *gc.C) { cl := client.NewClient(s.cred, identity.AuthUserPass, nil) err := cl.Authenticate() c.Assert(err, gc.ErrorMatches, "(.|\n)*x509: certificate signed by unknown authority") } func (s *localHTTPSSuite) TestNonValidatingClientAcceptsSelfSigned(c *gc.C) { cl := client.NewNonValidatingClient(s.cred, identity.AuthUserPass, nil) err := cl.Authenticate() c.Assert(err, gc.IsNil) // Requests into this client should be https:// URLs swiftURL, err := cl.MakeServiceURL("object-store", "", []string{"test_container"}) c.Assert(err, gc.IsNil) c.Assert(swiftURL[:8], gc.Equals, "https://") // We use swiftClient.CreateContainer to test a Binary request swiftClient := swift.New(cl) c.Assert(swiftClient.CreateContainer("test_container", swift.Private), gc.IsNil) // And we use List to test the JsonRequest contents, err := swiftClient.List("test_container", "", "", "", 0) c.Assert(err, gc.IsNil) c.Check(contents, gc.DeepEquals, []swift.ContainerContents{}) } func (s *localHTTPSSuite) setupPublicContainer(c *gc.C) string { // First set up a container that can be read publically authClient := client.NewNonValidatingClient(s.cred, identity.AuthUserPass, nil) authSwift := swift.New(authClient) err := authSwift.CreateContainer("test_container", swift.PublicRead) c.Assert(err, gc.IsNil) baseURL, err := authClient.MakeServiceURL("object-store", "", nil) c.Assert(err, gc.IsNil) c.Assert(baseURL[:8], gc.Equals, "https://") return baseURL } func (s *localHTTPSSuite) TestDefaultPublicClientRefusesSelfSigned(c *gc.C) { baseURL := s.setupPublicContainer(c) swiftClient := swift.New(client.NewPublicClient(baseURL, nil)) contents, err := swiftClient.List("test_container", "", "", "", 0) c.Assert(err, gc.ErrorMatches, "(.|\n)*x509: certificate signed by unknown authority") c.Assert(contents, gc.DeepEquals, []swift.ContainerContents(nil)) } func (s *localHTTPSSuite) TestNonValidatingPublicClientAcceptsSelfSigned(c *gc.C) { baseURL := s.setupPublicContainer(c) swiftClient := swift.New(client.NewNonValidatingPublicClient(baseURL, nil)) contents, err := swiftClient.List("test_container", "", "", "", 0) c.Assert(err, gc.IsNil) c.Assert(contents, gc.DeepEquals, []swift.ContainerContents{}) } func (s *localHTTPSSuite) TestAuthDiscover(c *gc.C) { cl := client.NewNonValidatingClient(s.cred, identity.AuthUserPass, nil) options, err := cl.IdentityAuthOptions() c.Assert(err, gc.IsNil) c.Assert(options, gc.DeepEquals, identity.AuthOptions{identity.AuthOption{Mode: 3, Endpoint: s.cred.URL + "/v3/"}, identity.AuthOption{Mode: 1, Endpoint: s.cred.URL + "/v2.0/"}}) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/errors/000077500000000000000000000000001414605405700214305ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/errors/errors.go000066400000000000000000000103741414605405700233000ustar00rootroot00000000000000// This package provides an Error implementation which knows about types of error, and which has support // for error causes. package errors import "fmt" type Code string const ( // Public available error types. // These errors are provided because they are specifically required by business logic in the callers. UnspecifiedError = Code("Unspecified") NotFoundError = Code("NotFound") DuplicateValueError = Code("DuplicateValue") TimeoutError = Code("Timeout") UnauthorisedError = Code("Unauthorised") NotImplementedError = Code("NotImplemented") ) // Error instances store an optional error cause. type Error interface { error Cause() error } type gooseError struct { error errcode Code cause error } // Type checks. var _ Error = (*gooseError)(nil) // Code returns the error code. func (err *gooseError) code() Code { if err.errcode != UnspecifiedError { return err.errcode } if e, ok := err.cause.(*gooseError); ok { return e.code() } return UnspecifiedError } // Cause returns the error cause. func (err *gooseError) Cause() error { return err.cause } // CausedBy returns true if this error or its cause are of the specified error code. func (err *gooseError) causedBy(code Code) bool { if err.code() == code { return true } if cause, ok := err.cause.(*gooseError); ok { return cause.code() == code } return false } // Error fulfills the error interface, taking account of any caused by error. func (err *gooseError) Error() string { if err.cause != nil { return fmt.Sprintf("%v\ncaused by: %v", err.error, err.cause) } return err.error.Error() } func IsNotFound(err error) bool { if e, ok := err.(*gooseError); ok { return e.causedBy(NotFoundError) } return false } func IsDuplicateValue(err error) bool { if e, ok := err.(*gooseError); ok { return e.causedBy(DuplicateValueError) } return false } func IsTimeout(err error) bool { if e, ok := err.(*gooseError); ok { return e.causedBy(TimeoutError) } return false } func IsUnauthorised(err error) bool { if e, ok := err.(*gooseError); ok { return e.causedBy(UnauthorisedError) } return false } func IsNotImplemented(err error) bool { if e, ok := err.(*gooseError); ok { return e.causedBy(NotImplementedError) } return false } // makeErrorf creates a new Error instance with the specified cause. func makeErrorf(code Code, cause error, format string, args ...interface{}) Error { return &gooseError{ errcode: code, error: fmt.Errorf(format, args...), cause: cause, } } // Newf creates a new Unspecified Error instance with the specified cause. func Newf(cause error, format string, args ...interface{}) Error { return makeErrorf(UnspecifiedError, cause, format, args...) } // NewNotFoundf creates a new NotFound Error instance with the specified cause. func NewNotFoundf(cause error, context interface{}, format string, args ...interface{}) Error { if format == "" { format = fmt.Sprintf("Not found: %s", context) } return makeErrorf(NotFoundError, cause, format, args...) } // NewDuplicateValuef creates a new DuplicateValue Error instance with the specified cause. func NewDuplicateValuef(cause error, context interface{}, format string, args ...interface{}) Error { if format == "" { format = fmt.Sprintf("Duplicate: %s", context) } return makeErrorf(DuplicateValueError, cause, format, args...) } // NewTimeoutf creates a new Timeout Error instance with the specified cause. func NewTimeoutf(cause error, context interface{}, format string, args ...interface{}) Error { if format == "" { format = fmt.Sprintf("Timeout: %s", context) } return makeErrorf(TimeoutError, cause, format, args...) } // NewUnauthorisedf creates a new Unauthorised Error instance with the specified cause. func NewUnauthorisedf(cause error, context interface{}, format string, args ...interface{}) Error { if format == "" { format = fmt.Sprintf("Unauthorised: %s", context) } return makeErrorf(UnauthorisedError, cause, format, args...) } // NewNotImplementedf creates a new NotImplemented Error instance with the specified cause. func NewNotImplementedf(cause error, context interface{}, format string, args ...interface{}) Error { if format == "" { format = fmt.Sprintf("Not implemented: %s", context) } return makeErrorf(NotImplementedError, cause, format, args...) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/errors/errors_test.go000066400000000000000000000062571414605405700243440ustar00rootroot00000000000000package errors_test import ( "testing" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/errors" ) func Test(t *testing.T) { gc.TestingT(t) } type ErrorsSuite struct { } var _ = gc.Suite(&ErrorsSuite{}) func (s *ErrorsSuite) TestCreateSimpleNotFoundError(c *gc.C) { context := "context" err := errors.NewNotFoundf(nil, context, "") c.Assert(errors.IsNotFound(err), gc.Equals, true) c.Assert(err.Error(), gc.Equals, "Not found: context") } func (s *ErrorsSuite) TestCreateNotFoundError(c *gc.C) { context := "context" err := errors.NewNotFoundf(nil, context, "It was not found: %s", context) c.Assert(errors.IsNotFound(err), gc.Equals, true) c.Assert(err.Error(), gc.Equals, "It was not found: context") } func (s *ErrorsSuite) TestCreateSimpleDuplicateValueError(c *gc.C) { context := "context" err := errors.NewDuplicateValuef(nil, context, "") c.Assert(errors.IsDuplicateValue(err), gc.Equals, true) c.Assert(err.Error(), gc.Equals, "Duplicate: context") } func (s *ErrorsSuite) TestCreateDuplicateValueError(c *gc.C) { context := "context" err := errors.NewDuplicateValuef(nil, context, "It was duplicate: %s", context) c.Assert(errors.IsDuplicateValue(err), gc.Equals, true) c.Assert(err.Error(), gc.Equals, "It was duplicate: context") } func (s *ErrorsSuite) TestCreateSimpleUnauthorisedfError(c *gc.C) { context := "context" err := errors.NewUnauthorisedf(nil, context, "") c.Assert(errors.IsUnauthorised(err), gc.Equals, true) c.Assert(err.Error(), gc.Equals, "Unauthorised: context") } func (s *ErrorsSuite) TestCreateUnauthorisedfError(c *gc.C) { context := "context" err := errors.NewUnauthorisedf(nil, context, "It was unauthorised: %s", context) c.Assert(errors.IsUnauthorised(err), gc.Equals, true) c.Assert(err.Error(), gc.Equals, "It was unauthorised: context") } func (s *ErrorsSuite) TestCreateSimpleNotImplementedfError(c *gc.C) { context := "context" err := errors.NewNotImplementedf(nil, context, "") c.Assert(errors.IsNotImplemented(err), gc.Equals, true) c.Assert(err.Error(), gc.Equals, "Not implemented: context") } func (s *ErrorsSuite) TestCreateNotImplementedfError(c *gc.C) { context := "context" err := errors.NewNotImplementedf(nil, context, "It was not implemented: %s", context) c.Assert(errors.IsNotImplemented(err), gc.Equals, true) c.Assert(err.Error(), gc.Equals, "It was not implemented: context") } func (s *ErrorsSuite) TestErrorCause(c *gc.C) { rootCause := errors.NewNotFoundf(nil, "some value", "") // Construct a new error, based on a not found root cause. err := errors.Newf(rootCause, "an error occurred") c.Assert(err.Cause(), gc.Equals, rootCause) // Check the other error attributes. c.Assert(err.Error(), gc.Equals, "an error occurred\ncaused by: Not found: some value") } func (s *ErrorsSuite) TestErrorIsType(c *gc.C) { rootCause := errors.NewNotFoundf(nil, "some value", "") // Construct a new error, based on a not found root cause. err := errors.Newf(rootCause, "an error occurred") // Check that the error is not falsely identified as something it is not. c.Assert(errors.IsDuplicateValue(err), gc.Equals, false) // Check that the error is correctly identified as a not found error. c.Assert(errors.IsNotFound(err), gc.Equals, true) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/glance/000077500000000000000000000000001414605405700213455ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/glance/glance.go000066400000000000000000000114131414605405700231250ustar00rootroot00000000000000// goose/glance - Go package to interact with OpenStack Image Service (Glance) API. // For V2 functions see http://developer.openstack.org/api-ref/image/v2/index.html // For all other functions see http://developer.openstack.org/api-ref/compute/#list-images package glance import ( "fmt" "net/http" "gopkg.in/goose.v1/client" "gopkg.in/goose.v1/errors" goosehttp "gopkg.in/goose.v1/http" ) // API URL parts. const ( apiImages = "/images" apiImagesDetail = "/images/detail" ) // Client provides a means to access the OpenStack Image Service. type Client struct { client client.Client } // New creates a new Client. func New(client client.Client) *Client { return &Client{client} } // Link describes a link to an image in OpenStack. type Link struct { Href string Rel string Type string } // Image describes an OpenStack image. type Image struct { Id string Name string Links []Link } // ListImages lists IDs, names, and links for available images. func (c *Client) ListImages() ([]Image, error) { var resp struct { Images []Image } requestData := goosehttp.RequestData{RespValue: &resp, ExpectedStatus: []int{http.StatusOK}} err := c.client.SendRequest(client.GET, "compute", "v2", apiImages, &requestData) if err != nil { return nil, errors.Newf(err, "failed to get list of images") } return resp.Images, nil } // ImageMetadata describes metadata of an image type ImageMetadata struct { Architecture string State string `json:"image_state"` Location string `json:"image_location"` KernelId interface{} `json:"kernel_id"` ProjectId interface{} `json:"project_id"` RAMDiskId interface{} `json:"ramdisk_id"` OwnerId interface{} `json:"owner_id"` } // ImageDetail describes extended information about an image. type ImageDetail struct { Id string Name string Created string Updated string Progress int Status string MinimumRAM int `json:"minRam"` MinimumDisk int `json:"minDisk"` Links []Link Metadata ImageMetadata } // ListImageDetails lists all details for available images. func (c *Client) ListImagesDetail() ([]ImageDetail, error) { var resp struct { Images []ImageDetail } requestData := goosehttp.RequestData{RespValue: &resp} err := c.client.SendRequest(client.GET, "compute", "v2", apiImagesDetail, &requestData) if err != nil { return nil, errors.Newf(err, "failed to get list of image details") } return resp.Images, nil } // GetImageDetail lists details of the specified image. func (c *Client) GetImageDetail(imageId string) (*ImageDetail, error) { var resp struct { Image ImageDetail } url := fmt.Sprintf("%s/%s", apiImages, imageId) requestData := goosehttp.RequestData{RespValue: &resp} err := c.client.SendRequest(client.GET, "compute", "v2", url, &requestData) if err != nil { return nil, errors.Newf(err, "failed to get details of imageId: %s", imageId) } return &resp.Image, nil } // ImageDetailV2 describes extended information about an image for API v2.0. type ImageDetailV2 struct { Architecture string `json:",omitempty"` Checksum string ContainerFormat string `json:"container_format"` CreatedAt string `json:"created_at"` DirectUrl string DiskFormat string `json:"disk_format"` File string Id string HwVifModel string `json:"hw_vif_model,omitempty"` HwDiskModel string `json:"hw_disk_bus,omitempty"` Locations interface{} `json:",omitempty"` MinimumDisk int `json:"min_disk"` MinimumRAM int `json:"min_ram"` Name string Owner string OsType string `json:"os_type"` Protected bool Schema string Self string Size int Status string Tags []string UpdatedAt string `json:"updated_at"` VirtualSize string `json:"virtual_size"` Visibility string } // ListImagesV2 lists all details for available image, uses API v2.0. func (c *Client) ListImagesV2() ([]ImageDetailV2, error) { var resp struct { Images []ImageDetailV2 } requestData := goosehttp.RequestData{RespValue: &resp} err := c.client.SendRequest(client.GET, "image", "v2", apiImages, &requestData) if err != nil { return nil, errors.Newf(err, "failed to get list of image details (v2)") } return resp.Images, nil } // GetImageDetailV2 lists details of the specified image, uses API v2.0 func (c *Client) GetImageDetailV2(imageId string) (*ImageDetailV2, error) { var resp ImageDetailV2 url := fmt.Sprintf("%s/%s", apiImages, imageId) requestData := goosehttp.RequestData{RespValue: &resp} err := c.client.SendRequest(client.GET, "image", "v2", url, &requestData) if err != nil { return nil, errors.Newf(err, "failed to get details of imageId (v2): %s", imageId) } return &resp, nil } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/glance/glance_test.go000066400000000000000000000062751414605405700241760ustar00rootroot00000000000000package glance_test import ( "flag" "testing" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/client" "gopkg.in/goose.v1/glance" "gopkg.in/goose.v1/identity" ) func Test(t *testing.T) { gc.TestingT(t) } var live = flag.Bool("live", false, "Include live OpenStack (Canonistack) tests") type GlanceSuite struct { glance *glance.Client } func (s *GlanceSuite) SetUpSuite(c *gc.C) { if !*live { c.Skip("-live not provided") } cred, err := identity.CompleteCredentialsFromEnv() c.Assert(err, gc.IsNil) client := client.NewClient(cred, identity.AuthUserPass, nil) c.Assert(err, gc.IsNil) s.glance = glance.New(client) } var suite = gc.Suite(&GlanceSuite{}) func (s *GlanceSuite) TestListImages(c *gc.C) { images, err := s.glance.ListImages() c.Assert(err, gc.IsNil) c.Assert(images, gc.Not(gc.HasLen), 0) for _, ir := range images { c.Assert(ir.Id, gc.Not(gc.Equals), "") c.Assert(ir.Name, gc.Not(gc.Equals), "") for _, l := range ir.Links { c.Assert(l.Href, gc.Matches, "https?://.*") c.Assert(l.Rel, gc.Matches, "self|bookmark|alternate") } } } func (s *GlanceSuite) TestListImagesDetail(c *gc.C) { images, err := s.glance.ListImagesDetail() c.Assert(err, gc.IsNil) c.Assert(images, gc.Not(gc.HasLen), 0) for _, ir := range images { c.Assert(ir.Created, gc.Matches, `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*`) c.Assert(ir.Updated, gc.Matches, `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*`) c.Assert(ir.Id, gc.Not(gc.Equals), "") c.Assert(ir.Status, gc.Not(gc.Equals), "") c.Assert(ir.Name, gc.Not(gc.Equals), "") for _, l := range ir.Links { c.Assert(l.Href, gc.Matches, "https?://.*") c.Assert(l.Rel, gc.Matches, "self|bookmark|alternate") } m := ir.Metadata c.Assert(m.Architecture, gc.Matches, "i386|x86_64|") c.Assert(m.State, gc.Matches, "active|available|") } } func (s *GlanceSuite) TestGetImageDetail(c *gc.C) { images, err := s.glance.ListImagesDetail() c.Assert(err, gc.IsNil) firstImage := images[0] ir, err := s.glance.GetImageDetail(firstImage.Id) c.Assert(err, gc.IsNil) c.Assert(ir.Created, gc.Matches, firstImage.Created) c.Assert(ir.Updated, gc.Matches, firstImage.Updated) c.Assert(ir.Name, gc.Equals, firstImage.Name) c.Assert(ir.Status, gc.Equals, firstImage.Status) } func (s *GlanceSuite) TestListImagesV2(c *gc.C) { images, err := s.glance.ListImagesV2() c.Assert(err, gc.IsNil) c.Assert(images, gc.Not(gc.HasLen), 0) for _, ir := range images { c.Assert(ir.CreatedAt, gc.Matches, `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*`) c.Assert(ir.UpdatedAt, gc.Matches, `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*`) c.Assert(ir.Id, gc.Not(gc.Equals), "") c.Assert(ir.Status, gc.Not(gc.Equals), "") c.Assert(ir.Name, gc.Not(gc.Equals), "") c.Assert(ir.Self, gc.Not(gc.Equals), "") c.Assert(ir.Checksum, gc.Not(gc.Equals), "") } } func (s *GlanceSuite) TestGetImageDetailV2(c *gc.C) { images, err := s.glance.ListImagesV2() c.Assert(err, gc.IsNil) firstImage := images[0] ir, err := s.glance.GetImageDetailV2(firstImage.Id) c.Assert(err, gc.IsNil) c.Assert(ir.CreatedAt, gc.Matches, firstImage.CreatedAt) c.Assert(ir.UpdatedAt, gc.Matches, firstImage.UpdatedAt) c.Assert(ir.Name, gc.Equals, firstImage.Name) c.Assert(ir.Status, gc.Equals, firstImage.Status) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/goose.go000066400000000000000000000001771414605405700215640ustar00rootroot00000000000000// Copyright 2013 Canonical Ltd. // Licensed under the LGPLv3, see COPYING and COPYING.LESSER file for details. package goose golang-gopkg-goose.v1-0.0~git20170406.3228e4f/goose_test.go000066400000000000000000000002601414605405700226140ustar00rootroot00000000000000package goose import ( "testing" gc "gopkg.in/check.v1" ) func Test(t *testing.T) { gc.TestingT(t) } type GooseTestSuite struct { } var _ = gc.Suite(&GooseTestSuite{}) golang-gopkg-goose.v1-0.0~git20170406.3228e4f/http/000077500000000000000000000000001414605405700210735ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/http/client.go000066400000000000000000000236061414605405700227070ustar00rootroot00000000000000// An HTTP Client which sends json and binary requests, handling data marshalling and response processing. package http import ( "bytes" "crypto/tls" "encoding/json" "fmt" "io" "io/ioutil" "net/http" "net/url" "regexp" "strconv" "sync" "time" "gopkg.in/goose.v1" "gopkg.in/goose.v1/errors" "gopkg.in/goose.v1/logging" ) const ( contentTypeJSON = "application/json" contentTypeOctetStream = "application/octet-stream" ) func init() { // See https://code.google.com/p/go/issues/detail?id=4677 // We need to force the connection to close each time so that we don't // hit the above Go bug. roundTripper := http.DefaultClient.Transport if transport, ok := roundTripper.(*http.Transport); ok { transport.DisableKeepAlives = true } http.DefaultTransport.(*http.Transport).DisableKeepAlives = true } type Client struct { http.Client maxSendAttempts int } type ErrorResponse struct { Message string `json:"message"` Code int `json:"code"` Title string `json:"title"` } func (e *ErrorResponse) Error() string { return fmt.Sprintf("Failed: %d %s: %s", e.Code, e.Title, e.Message) } func unmarshallError(jsonBytes []byte) (*ErrorResponse, error) { var response ErrorResponse var transientObject = make(map[string]*json.RawMessage) if err := json.Unmarshal(jsonBytes, &transientObject); err != nil { return nil, err } for key, value := range transientObject { if err := json.Unmarshal(*value, &response); err != nil { return nil, err } response.Title = key break } if response.Code != 0 && response.Message != "" { return &response, nil } return nil, fmt.Errorf("Unparsable json error body: %q", jsonBytes) } type RequestData struct { ReqHeaders http.Header Params *url.Values ExpectedStatus []int ReqValue interface{} RespValue interface{} ReqReader io.Reader ReqLength int RespReader io.ReadCloser RespHeaders http.Header } const ( // The maximum number of times to try sending a request before we give up // (assuming any unsuccessful attempts can be sensibly tried again). MaxSendAttempts = 3 ) var insecureClient *http.Client var insecureClientMutex sync.Mutex // New returns a new goose http *Client using the default net/http client. func New() *Client { return &Client{*http.DefaultClient, MaxSendAttempts} } func NewNonSSLValidating() *Client { insecureClientMutex.Lock() httpClient := insecureClient if httpClient == nil { insecureConfig := &tls.Config{InsecureSkipVerify: true} insecureTransport := &http.Transport{TLSClientConfig: insecureConfig} insecureClient = &http.Client{Transport: insecureTransport} httpClient = insecureClient } insecureClientMutex.Unlock() return &Client{*httpClient, MaxSendAttempts} } func gooseAgent() string { return fmt.Sprintf("goose (%s)", goose.Version) } func createHeaders(extraHeaders http.Header, contentType, authToken string, payloadExists bool) http.Header { headers := make(http.Header) if extraHeaders != nil { for header, values := range extraHeaders { for _, value := range values { headers.Add(header, value) } } } if authToken != "" { headers.Set("X-Auth-Token", authToken) } if payloadExists { headers.Add("Content-Type", contentType) } headers.Add("Accept", contentType) headers.Add("User-Agent", gooseAgent()) return headers } // JsonRequest JSON encodes and sends the object in reqData.ReqValue (if any) to the specified URL. // Optional method arguments are passed using the RequestData object. // Relevant RequestData fields: // ReqHeaders: additional HTTP header values to add to the request. // ExpectedStatus: the allowed HTTP response status values, else an error is returned. // ReqValue: the data object to send. // RespValue: the data object to decode the result into. func (c *Client) JsonRequest(method, url, token string, reqData *RequestData, logger logging.CompatLogger) (err error) { err = nil var body []byte if reqData.Params != nil { url += "?" + reqData.Params.Encode() } if reqData.ReqValue != nil { body, err = json.Marshal(reqData.ReqValue) if err != nil { err = errors.Newf(err, "failed marshalling the request body") return } } headers := createHeaders(reqData.ReqHeaders, contentTypeJSON, token, len(body) != 0) resp, err := c.sendRequest( method, url, bytes.NewReader(body), len(body), headers, reqData.ExpectedStatus, logging.FromCompat(logger), ) if err != nil { return } reqData.RespHeaders = resp.Header defer resp.Body.Close() respData, err := ioutil.ReadAll(resp.Body) if err != nil { err = errors.Newf(err, "failed reading the response body") return } if len(respData) > 0 { if reqData.RespValue != nil { err = json.Unmarshal(respData, &reqData.RespValue) if err != nil { err = errors.Newf(err, "failed unmarshaling the response body: %s", respData) } } } return } // Sends the byte array in reqData.ReqValue (if any) to the specified URL. // Optional method arguments are passed using the RequestData object. // Relevant RequestData fields: // ReqHeaders: additional HTTP header values to add to the request. // ExpectedStatus: the allowed HTTP response status values, else an error is returned. // ReqReader: an io.Reader providing the bytes to send. // RespReader: assigned an io.ReadCloser instance used to read the returned data.. func (c *Client) BinaryRequest(method, url, token string, reqData *RequestData, logger logging.CompatLogger) (err error) { err = nil if reqData.Params != nil { url += "?" + reqData.Params.Encode() } headers := createHeaders(reqData.ReqHeaders, contentTypeOctetStream, token, reqData.ReqLength != 0) resp, err := c.sendRequest( method, url, reqData.ReqReader, reqData.ReqLength, headers, reqData.ExpectedStatus, logging.FromCompat(logger), ) if err != nil { return } reqData.RespHeaders = resp.Header if reqData.RespReader != nil { reqData.RespReader = resp.Body } else { resp.Body.Close() } return } // Sends the specified request to URL and checks that the HTTP response status is as expected. // reqReader: a reader returning the data to send. // length: the number of bytes to send. // headers: HTTP headers to include with the request. // expectedStatus: a slice of allowed response status codes. func (c *Client) sendRequest( method, URL string, reqReader io.Reader, length int, headers http.Header, expectedStatus []int, logger logging.Logger, ) (*http.Response, error) { reqData := make([]byte, length) if reqReader != nil { nrRead, err := io.ReadFull(reqReader, reqData) if err != nil { err = errors.Newf(err, "failed reading the request data, read %v of %v bytes", nrRead, length) return nil, err } } rawResp, err := c.sendRateLimitedRequest(method, URL, headers, reqData, logger) if err != nil { return nil, err } foundStatus := false if len(expectedStatus) == 0 { expectedStatus = []int{http.StatusOK} } for _, status := range expectedStatus { if rawResp.StatusCode == status { foundStatus = true break } } if !foundStatus && len(expectedStatus) > 0 { err = handleError(URL, rawResp) rawResp.Body.Close() return nil, err } return rawResp, err } func (c *Client) sendRateLimitedRequest( method, URL string, headers http.Header, reqData []byte, logger logging.Logger, ) (resp *http.Response, err error) { for i := 0; i < c.maxSendAttempts; i++ { var reqReader io.Reader if reqData != nil { reqReader = bytes.NewReader(reqData) } req, err := http.NewRequest(method, URL, reqReader) if err != nil { err = errors.Newf(err, "failed creating the request %s", URL) return nil, err } for header, values := range headers { for _, value := range values { req.Header.Add(header, value) } } req.ContentLength = int64(len(reqData)) resp, err = c.Do(req) if err != nil { return nil, errors.Newf(err, "failed executing the request %s", URL) } if resp.StatusCode != http.StatusRequestEntityTooLarge || resp.Header.Get("Retry-After") == "" { return resp, nil } resp.Body.Close() retryAfter, err := strconv.ParseFloat(resp.Header.Get("Retry-After"), 32) if err != nil { return nil, errors.Newf(err, "Invalid Retry-After header %s", URL) } if retryAfter == 0 { return nil, errors.Newf(err, "Resource limit exeeded at URL %s", URL) } logger.Debugf("Too many requests, retrying in %dms.", int(retryAfter*1000)) time.Sleep(time.Duration(retryAfter) * time.Second) } return nil, errors.Newf(err, "Maximum number of attempts (%d) reached sending request to %s", c.maxSendAttempts, URL) } type HttpError struct { StatusCode int Data map[string][]string url string responseMessage string } func (e *HttpError) Error() string { return fmt.Sprintf("request (%s) returned unexpected status: %d; error info: %v", e.url, e.StatusCode, e.responseMessage, ) } // The HTTP response status code was not one of those expected, so we construct an error. // NotFound (404) codes have their own NotFound error type. // We also make a guess at duplicate value errors. func handleError(URL string, resp *http.Response) error { errBytes, _ := ioutil.ReadAll(resp.Body) errInfo := string(errBytes) // Check if we have a JSON representation of the failure, if so decode it. if resp.Header.Get("Content-Type") == contentTypeJSON { errorResponse, err := unmarshallError(errBytes) //TODO (hduran-8): Obtain a logger and log the error if err == nil { errInfo = errorResponse.Error() } } httpError := &HttpError{ resp.StatusCode, map[string][]string(resp.Header), URL, errInfo, } switch resp.StatusCode { case http.StatusNotFound: return errors.NewNotFoundf(httpError, "", "Resource at %s not found", URL) case http.StatusForbidden, http.StatusUnauthorized: return errors.NewUnauthorisedf(httpError, "", "Unauthorised URL %s", URL) case http.StatusBadRequest: dupExp, _ := regexp.Compile(".*already exists.*") if dupExp.Match(errBytes) { return errors.NewDuplicateValuef(httpError, "", string(errBytes)) } } return httpError } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/http/client_test.go000066400000000000000000000211521414605405700237400ustar00rootroot00000000000000package http import ( "bytes" "fmt" "io/ioutil" "net/http" "testing" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/testing/httpsuite" ) func Test(t *testing.T) { gc.TestingT(t) } type LoopingHTTPSuite struct { httpsuite.HTTPSuite } func (s *LoopingHTTPSuite) setupLoopbackRequest() (*http.Header, chan string, *Client) { var headers http.Header bodyChan := make(chan string, 1) handler := func(resp http.ResponseWriter, req *http.Request) { headers = req.Header bodyBytes, _ := ioutil.ReadAll(req.Body) req.Body.Close() bodyChan <- string(bodyBytes) resp.Header().Add("Content-Length", "0") resp.Header().Add("Testing", "true") resp.WriteHeader(http.StatusNoContent) resp.Write([]byte{}) } s.Mux.HandleFunc("/", handler) client := New() return &headers, bodyChan, client } type HTTPClientTestSuite struct { LoopingHTTPSuite } type HTTPSClientTestSuite struct { LoopingHTTPSuite } var _ = gc.Suite(&HTTPClientTestSuite{}) var _ = gc.Suite(&HTTPSClientTestSuite{LoopingHTTPSuite{httpsuite.HTTPSuite{UseTLS: true}}}) func (s *HTTPClientTestSuite) assertHeaderValues(c *gc.C, token string) { emptyHeaders := http.Header{} headers := createHeaders(emptyHeaders, "content-type", token, true) contentTypes := []string{"content-type"} headerData := map[string][]string{ "Content-Type": contentTypes, "Accept": contentTypes, "User-Agent": {gooseAgent()}} if token != "" { headerData["X-Auth-Token"] = []string{token} } expectedHeaders := http.Header(headerData) c.Assert(headers, gc.DeepEquals, expectedHeaders) c.Assert(emptyHeaders, gc.DeepEquals, http.Header{}) } func (s *HTTPClientTestSuite) TestCreateHeadersNoToken(c *gc.C) { s.assertHeaderValues(c, "") } func (s *HTTPClientTestSuite) TestCreateHeadersWithToken(c *gc.C) { s.assertHeaderValues(c, "token") } func (s *HTTPClientTestSuite) TestCreateHeadersCopiesSupplied(c *gc.C) { initialHeaders := make(http.Header) initialHeaders["Foo"] = []string{"Bar"} contentType := contentTypeJSON contentTypes := []string{contentType} headers := createHeaders(initialHeaders, contentType, "", true) // it should not change the headers passed in c.Assert(initialHeaders, gc.DeepEquals, http.Header{"Foo": []string{"Bar"}}) // The initial headers should be in the output c.Assert(headers, gc.DeepEquals, http.Header{"Foo": []string{"Bar"}, "Content-Type": contentTypes, "Accept": contentTypes, "User-Agent": []string{gooseAgent()}}) } func (s *HTTPClientTestSuite) TestBinaryRequestSetsUserAgent(c *gc.C) { headers, _, client := s.setupLoopbackRequest() req := &RequestData{ExpectedStatus: []int{http.StatusNoContent}} err := client.BinaryRequest("POST", s.Server.URL, "", req, nil) c.Assert(err, gc.IsNil) agent := headers.Get("User-Agent") c.Check(agent, gc.Not(gc.Equals), "") c.Check(agent, gc.Equals, gooseAgent()) c.Check(req.RespHeaders.Get("Testing"), gc.Equals, "true") } func (s *HTTPClientTestSuite) TestJSONRequestSetsUserAgent(c *gc.C) { headers, _, client := s.setupLoopbackRequest() req := &RequestData{ExpectedStatus: []int{http.StatusNoContent}} err := client.JsonRequest("POST", s.Server.URL, "", req, nil) c.Assert(err, gc.IsNil) agent := headers.Get("User-Agent") c.Check(agent, gc.Not(gc.Equals), "") c.Check(agent, gc.Equals, gooseAgent()) c.Check(req.RespHeaders.Get("Testing"), gc.Equals, "true") } func (s *HTTPClientTestSuite) TestBinaryRequestSetsContentLength(c *gc.C) { headers, bodyChan, client := s.setupLoopbackRequest() content := "binary\ncontent\n" req := &RequestData{ ExpectedStatus: []int{http.StatusNoContent}, ReqReader: bytes.NewBufferString(content), ReqLength: len(content), } err := client.BinaryRequest("POST", s.Server.URL, "", req, nil) c.Assert(err, gc.IsNil) encoding := headers.Get("Transfer-Encoding") c.Check(encoding, gc.Equals, "") length := headers.Get("Content-Length") c.Check(length, gc.Equals, fmt.Sprintf("%d", len(content))) body, ok := <-bodyChan c.Assert(ok, gc.Equals, true) c.Check(body, gc.Equals, content) } func (s *HTTPClientTestSuite) TestJSONRequestSetsContentLength(c *gc.C) { headers, bodyChan, client := s.setupLoopbackRequest() reqMap := map[string]string{"key": "value"} req := &RequestData{ ExpectedStatus: []int{http.StatusNoContent}, ReqValue: reqMap, } err := client.JsonRequest("POST", s.Server.URL, "", req, nil) c.Assert(err, gc.IsNil) encoding := headers.Get("Transfer-Encoding") c.Check(encoding, gc.Equals, "") length := headers.Get("Content-Length") body, ok := <-bodyChan c.Assert(ok, gc.Equals, true) c.Check(body, gc.Not(gc.Equals), "") c.Check(length, gc.Equals, fmt.Sprintf("%d", len(body))) } func (s *HTTPClientTestSuite) TestJSONRequestNoPayload(c *gc.C) { headers, bodyChan, client := s.setupLoopbackRequest() req := &RequestData{ ExpectedStatus: []int{http.StatusNoContent}, ReqValue: nil, } err := client.JsonRequest("POST", s.Server.URL, "", req, nil) c.Assert(err, gc.IsNil) encoding := headers.Get("Transfer-Encoding") c.Check(encoding, gc.Equals, "") length := headers.Get("Content-Length") ctype := headers.Get("Content-Type") body, ok := <-bodyChan c.Assert(ok, gc.Equals, true) c.Check(body, gc.Equals, "") c.Check(length, gc.Equals, "0") c.Check(ctype, gc.Equals, "") } func (s *HTTPClientTestSuite) TestBinaryRequestSetsToken(c *gc.C) { headers, _, client := s.setupLoopbackRequest() req := &RequestData{ExpectedStatus: []int{http.StatusNoContent}} err := client.BinaryRequest("POST", s.Server.URL, "token", req, nil) c.Assert(err, gc.IsNil) agent := headers.Get("X-Auth-Token") c.Check(agent, gc.Equals, "token") } func (s *HTTPClientTestSuite) TestJSONRequestSetsToken(c *gc.C) { headers, _, client := s.setupLoopbackRequest() req := &RequestData{ExpectedStatus: []int{http.StatusNoContent}} err := client.JsonRequest("POST", s.Server.URL, "token", req, nil) c.Assert(err, gc.IsNil) agent := headers.Get("X-Auth-Token") c.Check(agent, gc.Equals, "token") } func (s *HTTPClientTestSuite) TestHttpTransport(c *gc.C) { transport := http.DefaultTransport.(*http.Transport) c.Assert(transport.DisableKeepAlives, gc.Equals, true) } func (s *HTTPSClientTestSuite) TestDefaultClientRejectSelfSigned(c *gc.C) { _, _, client := s.setupLoopbackRequest() req := &RequestData{ExpectedStatus: []int{http.StatusNoContent}} err := client.BinaryRequest("POST", s.Server.URL, "", req, nil) c.Assert(err, gc.NotNil) c.Check(err, gc.ErrorMatches, "(.|\\n)*x509: certificate signed by unknown authority") } func (s *HTTPSClientTestSuite) TestInsecureClientAllowsSelfSigned(c *gc.C) { headers, _, _ := s.setupLoopbackRequest() client := NewNonSSLValidating() req := &RequestData{ExpectedStatus: []int{http.StatusNoContent}} err := client.BinaryRequest("POST", s.Server.URL, "", req, nil) c.Assert(err, gc.IsNil) agent := headers.Get("User-Agent") c.Check(agent, gc.Not(gc.Equals), "") c.Check(agent, gc.Equals, gooseAgent()) } func (s *HTTPSClientTestSuite) TestProperlyFormattedJsonUnmarshalling(c *gc.C) { validJSON := `{"itemNotFound": {"message": "A Meaningful error", "code": 404}}` unmarshalled, err := unmarshallError([]byte(validJSON)) c.Assert(err, gc.IsNil) c.Check(unmarshalled.Code, gc.Equals, 404) c.Check(unmarshalled.Title, gc.Equals, "itemNotFound") c.Check(unmarshalled.Message, gc.Equals, "A Meaningful error") } func (s *HTTPSClientTestSuite) TestImproperlyFormattedJSONUnmarshalling(c *gc.C) { invalidJSON := `This string is not a valid JSON` unmarshalled, err := unmarshallError([]byte(invalidJSON)) c.Assert(err, gc.NotNil) c.Assert(unmarshalled, gc.IsNil) c.Check(err, gc.ErrorMatches, "invalid character 'T' looking for beginning of value") } func (s *HTTPSClientTestSuite) TestJSONMissingCodeUnmarshalling(c *gc.C) { missingCodeJSON := `{"itemNotFound": {"message": "A Meaningful error"}}` unmarshalled, err := unmarshallError([]byte(missingCodeJSON)) c.Assert(err, gc.NotNil) c.Assert(unmarshalled, gc.IsNil) c.Check(err, gc.ErrorMatches, `Unparsable json error body: "{\\"itemNotFound\\": {\\"message\\": \\"A Meaningful error\\"}}"`) } func (s *HTTPSClientTestSuite) TestJSONMissingMessageUnmarshalling(c *gc.C) { missingMessageJSON := `{"itemNotFound": {"code": 404}}` unmarshalled, err := unmarshallError([]byte(missingMessageJSON)) c.Assert(err, gc.NotNil) c.Assert(unmarshalled, gc.IsNil) c.Check(err, gc.ErrorMatches, `Unparsable json error body: "{\\"itemNotFound\\": {\\"code\\": 404}}"`) } func (s *HTTPSClientTestSuite) TestBrokenBodyJSONUnmarshalling(c *gc.C) { invalidBodyJSON := `{"itemNotFound": {}}` unmarshalled, err := unmarshallError([]byte(invalidBodyJSON)) c.Assert(err, gc.NotNil) c.Assert(unmarshalled, gc.IsNil) c.Check(err, gc.ErrorMatches, `Unparsable json error body: \"{\\\"itemNotFound\\\": {}}\"`) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/identity/000077500000000000000000000000001414605405700217455ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/identity/identity.go000066400000000000000000000171751414605405700241400ustar00rootroot00000000000000// goose/identity - Go package to interact with OpenStack Identity (Keystone) API. package identity import ( "fmt" "net/http" "os" "reflect" "strings" goosehttp "gopkg.in/goose.v1/http" "gopkg.in/goose.v1/logging" ) // AuthMode defines the authentication method to use (see Auth* // constants below). type AuthMode int const ( AuthLegacy = AuthMode(iota) // Legacy authentication AuthUserPass // Username + password authentication AuthKeyPair // Access/secret key pair authentication AuthUserPassV3 // Username + password authentication (v3 API) ) func (a AuthMode) String() string { switch a { case AuthKeyPair: return "Access/Secret Key Authentication" case AuthLegacy: return "Legacy Authentication" case AuthUserPass: return "Username/password Authentication" case AuthUserPassV3: return "Username/password Authentication (Version 3)" } panic(fmt.Errorf("Unknown athentication type: %d", a)) } type AuthOption struct { Mode AuthMode Endpoint string } type AuthOptions []AuthOption type ServiceURLs map[string]string // AuthDetails defines all the necessary information, needed for an // authenticated session with OpenStack. type AuthDetails struct { Token string TenantId string UserId string Domain string RegionServiceURLs map[string]ServiceURLs // Service type to endpoint URLs for each region } // Credentials defines necessary parameters for authentication. type Credentials struct { URL string // The URL to authenticate against User string // The username to authenticate as Secrets string // The secrets to pass Region string // Region to send requests to TenantName string // The project name for this connection Domain string `credentials:"optional"` // The domain for authorization (new in keystone v3) UserDomain string `credentials:"optional"` // The owning domain for this user (new in keystone v3) ProjectDomain string `credentials:"optional"` // The project domain for authorization (new in keystone v3) } // Authenticator is implemented by each authentication method. type Authenticator interface { Auth(creds *Credentials) (*AuthDetails, error) } // getConfig returns the value of the first available environment // variable, among the given ones. func getConfig(envVars []string) (value string) { value = "" for _, v := range envVars { value = os.Getenv(v) if value != "" { break } } return } // The following variables hold the names of environment variables // that are used by CredentialsFromEnv to populate a Credentials // value. The most preferred names are at the start of the slices. var ( // CredEnvAuthURL is used for Credentials.URL. CredEnvAuthURL = []string{ "OS_AUTH_URL", } // CredEnvUser is used for Credentials.User. CredEnvUser = []string{ "OS_USERNAME", "NOVA_USERNAME", "OS_ACCESS_KEY", "NOVA_API_KEY", } // CredEnvSecrets is used for Credentials.Secrets. CredEnvSecrets = []string{ "OS_PASSWORD", "NOVA_PASSWORD", "OS_SECRET_KEY", // Apparently some clients did use this. "AWS_SECRET_ACCESS_KEY", // This is manifestly a misspelling but we leave // it here just in case anyone did actually use it. "EC2_SECRET_KEYS", } // CredEnvRegion is used for Credentials.Region. CredEnvRegion = []string{ "OS_REGION_NAME", "NOVA_REGION", } // CredEnvTenantName is used for Credentials.TenantName. CredEnvTenantName = []string{ "OS_PROJECT_NAME", "OS_TENANT_NAME", "NOVA_PROJECT_ID", } // The following env vars are set according to what type // of keystone v3 domain authorization is required. CredEnvDefaultDomainName = []string{ "OS_DEFAULT_DOMAIN_NAME", } CredEnvProjectDomainName = []string{ "OS_PROJECT_DOMAIN_NAME", } CredEnvUserDomainName = []string{ "OS_USER_DOMAIN_NAME", } CredEnvDomainName = []string{ "OS_DOMAIN_NAME", } ) // CredentialsFromEnv creates and initializes the credentials from the // environment variables. func CredentialsFromEnv() *Credentials { cred := &Credentials{ URL: getConfig(CredEnvAuthURL), User: getConfig(CredEnvUser), Secrets: getConfig(CredEnvSecrets), Region: getConfig(CredEnvRegion), TenantName: getConfig(CredEnvTenantName), Domain: getConfig(CredEnvDomainName), UserDomain: getConfig(CredEnvUserDomainName), ProjectDomain: getConfig(CredEnvProjectDomainName), } defaultDomain := getConfig(CredEnvDefaultDomainName) if defaultDomain != "" { if cred.ProjectDomain == "" { cred.ProjectDomain = defaultDomain } if cred.UserDomain == "" { cred.UserDomain = defaultDomain } } return cred } // CompleteCredentialsFromEnv gets and verifies all the required // authentication parameters have values in the environment. func CompleteCredentialsFromEnv() (cred *Credentials, err error) { cred = CredentialsFromEnv() v := reflect.ValueOf(cred).Elem() t := v.Type() for i := 0; i < v.NumField(); i++ { f := v.Field(i) tag := t.Field(i).Tag.Get("credentials") if f.String() == "" && tag != "optional" { err = fmt.Errorf("required environment variable not set for credentials attribute: %s", t.Field(i).Name) } } return } // NewAuthenticator creates an authenticator matching the supplied AuthMode. // The httpclient is allowed to be nil, the Authenticator will just use the // default http.Client func NewAuthenticator(authMode AuthMode, httpClient *goosehttp.Client) Authenticator { if httpClient == nil { httpClient = goosehttp.New() } switch authMode { default: panic(fmt.Errorf("Invalid identity authorisation mode: %d", authMode)) case AuthLegacy: return &Legacy{client: httpClient} case AuthUserPass: return &UserPass{client: httpClient} case AuthKeyPair: return &KeyPair{client: httpClient} case AuthUserPassV3: return &V3UserPass{client: httpClient} } } type authInformationLink struct { Href string `json:"href"` Rel string `json:"base"` } type authInformationMediaType struct { Base string `json:"base"` MediaType string `json:"type"` } type authInformationValue struct { ID string `json:"id"` Links []authInformationLink `json:"links"` MediaTypes []authInformationMediaType `json:"media-types"` Status string `json:"status"` Updated string `json:"updates"` } type authInformationVersions struct { Values []authInformationValue `json:"values"` } type authInformation struct { Versions authInformationVersions `json:"versions"` } // FetchAuthOptions returns the authentication options advertised by this // openstack. func FetchAuthOptions(url string, client *goosehttp.Client, compatLogger logging.CompatLogger) (AuthOptions, error) { var resp authInformation req := goosehttp.RequestData{ RespValue: &resp, ExpectedStatus: []int{ http.StatusMultipleChoices, }, } if err := client.JsonRequest("GET", url, "", &req, nil); err != nil { return nil, fmt.Errorf("request available auth options: %v", err) } logger := logging.FromCompat(compatLogger) var auths AuthOptions if len(resp.Versions.Values) > 0 { for _, version := range resp.Versions.Values { // TODO(perrito666) figure more cases. link := "" if len(version.Links) > 0 { link = version.Links[0].Href } var opt AuthOption switch { case strings.HasPrefix(version.ID, "v3"): opt = AuthOption{Mode: AuthUserPassV3, Endpoint: link} case strings.HasPrefix(version.ID, "v2"): opt = AuthOption{Mode: AuthUserPass, Endpoint: link} default: if logger != nil { logger.Debugf("Unknown authentication version %q\n", version.ID) } } auths = append(auths, opt) } } return auths, nil } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/identity/identity_test.go000066400000000000000000000175621414605405700251770ustar00rootroot00000000000000package identity import ( "os" gc "gopkg.in/check.v1" goosehttp "gopkg.in/goose.v1/http" "gopkg.in/goose.v1/testing/envsuite" ) type CredentialsTestSuite struct { // Isolate all of these tests from the real Environ. envsuite.EnvSuite } type NewAuthenticatorSuite struct{} var _ = gc.Suite(&CredentialsTestSuite{}) var _ = gc.Suite(&NewAuthenticatorSuite{}) func (s *CredentialsTestSuite) TestCredentialsFromEnv(c *gc.C) { var scenarios = []struct { summary string env map[string]string username string password string tenant string region string domain string authURL string }{ {summary: "Old 'NOVA' style creds", env: map[string]string{ "NOVA_USERNAME": "test-user", "NOVA_PASSWORD": "test-pass", "NOVA_API_KEY": "test-access-key", "EC2_SECRET_KEYS": "test-secret-key", "NOVA_PROJECT_ID": "tenant-name", "NOVA_REGION": "region", }, username: "test-user", password: "test-pass", tenant: "tenant-name", region: "region", }, {summary: "New 'OS' style environment", env: map[string]string{ "OS_USERNAME": "test-user", "OS_PASSWORD": "test-pass", "OS_ACCESS_KEY": "test-access-key", "OS_SECRET_KEY": "test-secret-key", "OS_TENANT_NAME": "tenant-name", "OS_REGION_NAME": "region", "OS_DOMAIN_NAME": "domain-name", }, username: "test-user", password: "test-pass", tenant: "tenant-name", region: "region", domain: "domain-name", }, } for _, scenario := range scenarios { for key, value := range scenario.env { os.Setenv(key, value) } creds := CredentialsFromEnv() c.Check(creds.URL, gc.Equals, scenario.authURL) c.Check(creds.User, gc.Equals, scenario.username) c.Check(creds.Secrets, gc.Equals, scenario.password) c.Check(creds.Region, gc.Equals, scenario.region) c.Check(creds.TenantName, gc.Equals, scenario.tenant) } } func (s *CredentialsTestSuite) TestCompleteCredentialsFromEnvValid(c *gc.C) { env := map[string]string{ "OS_AUTH_URL": "http://auth", "OS_USERNAME": "test-user", "OS_PASSWORD": "test-pass", "OS_ACCESS_KEY": "test-access-key", "OS_SECRET_KEY": "test-secret-key", "OS_PROJECT_NAME": "tenant-name", "OS_REGION_NAME": "region", "OS_DOMAIN_NAME": "domain-name", "OS_PROJECT_DOMAIN_NAME": "project-domain-name", "OS_USER_DOMAIN_NAME": "user-domain-name", // ignored because user and project domains set "OS_DEFAULT_DOMAIN_NAME": "default-domain-name", } for key, value := range env { os.Setenv(key, value) } creds, err := CompleteCredentialsFromEnv() c.Assert(err, gc.IsNil) c.Check(creds.URL, gc.Equals, "http://auth") c.Check(creds.User, gc.Equals, "test-user") c.Check(creds.Secrets, gc.Equals, "test-pass") c.Check(creds.Region, gc.Equals, "region") c.Check(creds.TenantName, gc.Equals, "tenant-name") c.Check(creds.Domain, gc.Equals, "domain-name") c.Check(creds.ProjectDomain, gc.Equals, "project-domain-name") c.Check(creds.UserDomain, gc.Equals, "user-domain-name") } func (s *CredentialsTestSuite) TestCompleteCredentialsFromEnvDefaultDomain(c *gc.C) { env := map[string]string{ "OS_AUTH_URL": "http://auth", "OS_USERNAME": "test-user", "OS_PASSWORD": "test-pass", "OS_ACCESS_KEY": "test-access-key", "OS_SECRET_KEY": "test-secret-key", "OS_PROJECT_NAME": "tenant-name", "OS_REGION_NAME": "region", "OS_DOMAIN_NAME": "domain-name", "OS_DEFAULT_DOMAIN_NAME": "default-domain-name", } for key, value := range env { os.Setenv(key, value) } creds, err := CompleteCredentialsFromEnv() c.Assert(err, gc.IsNil) c.Check(creds.URL, gc.Equals, "http://auth") c.Check(creds.User, gc.Equals, "test-user") c.Check(creds.Secrets, gc.Equals, "test-pass") c.Check(creds.Region, gc.Equals, "region") c.Check(creds.TenantName, gc.Equals, "tenant-name") c.Check(creds.Domain, gc.Equals, "domain-name") c.Check(creds.ProjectDomain, gc.Equals, "default-domain-name") c.Check(creds.UserDomain, gc.Equals, "default-domain-name") } // An error is returned if not all required environment variables are set. func (s *CredentialsTestSuite) TestCompleteCredentialsFromEnvInvalid(c *gc.C) { env := map[string]string{ "OS_AUTH_URL": "http://auth", "OS_USERNAME": "test-user", "OS_ACCESS_KEY": "test-access-key", "OS_PROJECT_NAME": "tenant-name", "OS_REGION_NAME": "region", "OS_DOMAIN_NAME": "domain-name", } for key, value := range env { os.Setenv(key, value) } _, err := CompleteCredentialsFromEnv() c.Assert(err, gc.Not(gc.IsNil)) c.Assert(err.Error(), gc.Matches, "required environment variable not set.*: Secrets") } func (s *CredentialsTestSuite) TestCompleteCredentialsFromEnvKeypair(c *gc.C) { env := map[string]string{ "OS_AUTH_URL": "http://auth", "OS_USERNAME": "", "OS_PASSWORD": "", "OS_ACCESS_KEY": "test-access-key", "OS_SECRET_KEY": "test-secret-key", "OS_PROJECT_NAME": "tenant-name", "OS_REGION_NAME": "region", "OS_DOMAIN_NAME": "domain-name", } for key, value := range env { os.Setenv(key, value) } creds, err := CompleteCredentialsFromEnv() c.Assert(err, gc.IsNil) c.Check(creds.URL, gc.Equals, "http://auth") c.Check(creds.User, gc.Equals, "test-access-key") c.Check(creds.Secrets, gc.Equals, "test-secret-key") c.Check(creds.Region, gc.Equals, "region") c.Check(creds.TenantName, gc.Equals, "tenant-name") c.Check(creds.Domain, gc.Equals, "domain-name") } func (s *CredentialsTestSuite) TestCompleteCredentialsFromEnvKeypairCompatibleEnvVars(c *gc.C) { env := map[string]string{ "OS_AUTH_URL": "http://auth", "OS_USERNAME": "", "OS_PASSWORD": "", "NOVA_API_KEY": "test-access-key", "EC2_SECRET_KEYS": "test-secret-key", "OS_TENANT_NAME": "tenant-name", "OS_REGION_NAME": "region", "OS_DOMAIN_NAME": "domain-name", } for key, value := range env { os.Setenv(key, value) } creds, err := CompleteCredentialsFromEnv() c.Assert(err, gc.IsNil) c.Check(creds.URL, gc.Equals, "http://auth") c.Check(creds.User, gc.Equals, "test-access-key") c.Check(creds.Secrets, gc.Equals, "test-secret-key") c.Check(creds.Region, gc.Equals, "region") c.Check(creds.TenantName, gc.Equals, "tenant-name") c.Check(creds.Domain, gc.Equals, "domain-name") } func (s *NewAuthenticatorSuite) TestUserPassNoHTTPClient(c *gc.C) { auth := NewAuthenticator(AuthUserPass, nil) userAuth, ok := auth.(*UserPass) c.Assert(ok, gc.Equals, true) c.Assert(userAuth.client, gc.NotNil) } func (s *NewAuthenticatorSuite) TestUserPassCustomHTTPClient(c *gc.C) { httpClient := goosehttp.New() auth := NewAuthenticator(AuthUserPass, httpClient) userAuth, ok := auth.(*UserPass) c.Assert(ok, gc.Equals, true) c.Assert(userAuth.client, gc.Equals, httpClient) } func (s *NewAuthenticatorSuite) TestKeyPairNoHTTPClient(c *gc.C) { auth := NewAuthenticator(AuthKeyPair, nil) keyPairAuth, ok := auth.(*KeyPair) c.Assert(ok, gc.Equals, true) c.Assert(keyPairAuth.client, gc.NotNil) } func (s *NewAuthenticatorSuite) TestKeyPairCustomHTTPClient(c *gc.C) { httpClient := goosehttp.New() auth := NewAuthenticator(AuthKeyPair, httpClient) keyPairAuth, ok := auth.(*KeyPair) c.Assert(ok, gc.Equals, true) c.Assert(keyPairAuth.client, gc.Equals, httpClient) } func (s *NewAuthenticatorSuite) TestLegacyNoHTTPClient(c *gc.C) { auth := NewAuthenticator(AuthLegacy, nil) legacyAuth, ok := auth.(*Legacy) c.Assert(ok, gc.Equals, true) c.Assert(legacyAuth.client, gc.NotNil) } func (s *NewAuthenticatorSuite) TestLegacyCustomHTTPClient(c *gc.C) { httpClient := goosehttp.New() auth := NewAuthenticator(AuthLegacy, httpClient) legacyAuth, ok := auth.(*Legacy) c.Assert(ok, gc.Equals, true) c.Assert(legacyAuth.client, gc.Equals, httpClient) } func (s *NewAuthenticatorSuite) TestUnknownMode(c *gc.C) { c.Assert(func() { NewAuthenticator(1235, nil) }, gc.PanicMatches, "Invalid identity authorisation mode: 1235") } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/identity/keypair.go000066400000000000000000000017341414605405700237450ustar00rootroot00000000000000package identity import ( goosehttp "gopkg.in/goose.v1/http" ) // KeyPair allows OpenStack cloud authentication using an access and // secret key. // // It implements Authenticator interface by providing the Auth method. type KeyPair struct { client *goosehttp.Client } type keypairCredentials struct { AccessKey string `json:"accessKey"` SecretKey string `json:"secretKey"` } type authKeypairRequest struct { KeypairCredentials keypairCredentials `json:"apiAccessKeyCredentials"` TenantName string `json:"tenantName"` } type authKeypairWrapper struct { Auth authKeypairRequest `json:"auth"` } func (u *KeyPair) Auth(creds *Credentials) (*AuthDetails, error) { if u.client == nil { u.client = goosehttp.New() } auth := authKeypairWrapper{Auth: authKeypairRequest{ KeypairCredentials: keypairCredentials{ AccessKey: creds.User, SecretKey: creds.Secrets, }, TenantName: creds.TenantName}} return keystoneAuth(u.client, auth, creds.URL) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/identity/keystone.go000066400000000000000000000046261414605405700241450ustar00rootroot00000000000000package identity import ( "fmt" goosehttp "gopkg.in/goose.v1/http" ) type endpoint struct { AdminURL string `json:"adminURL"` InternalURL string `json:"internalURL"` PublicURL string `json:"publicURL"` Region string `json:"region"` } type serviceResponse struct { Name string `json:"name"` Type string `json:"type"` Endpoints []endpoint } type tokenResponse struct { Expires string `json:"expires"` Id string `json:"id"` // Actual token string Tenant struct { Id string `json:"id"` Name string `json:"name"` // Description is a pointer since it may be null and this breaks Go < 1.1 Description *string `json:"description"` Enabled bool `json:"enabled"` } `json:"tenant"` } type roleResponse struct { Id string `json:"id"` Name string `json:"name"` TenantId string `json:"tenantId"` } type userResponse struct { Id string `json:"id"` Name string `json:"name"` Roles []roleResponse `json:"roles"` } type accessWrapper struct { Access accessResponse `json:"access"` } type accessResponse struct { ServiceCatalog []serviceResponse `json:"serviceCatalog"` Token tokenResponse `json:"token"` User userResponse `json:"user"` } // keystoneAuth authenticates to OpenStack cloud using keystone v2 authentication. // // Uses `client` to submit HTTP requests to `URL` // and posts `auth_data` as JSON. func keystoneAuth(client *goosehttp.Client, auth_data interface{}, URL string) (*AuthDetails, error) { var accessWrapper accessWrapper requestData := goosehttp.RequestData{ReqValue: auth_data, RespValue: &accessWrapper} err := client.JsonRequest("POST", URL, "", &requestData, nil) if err != nil { return nil, err } details := &AuthDetails{} access := accessWrapper.Access respToken := access.Token if respToken.Id == "" { return nil, fmt.Errorf("authentication failed") } details.Token = respToken.Id details.TenantId = respToken.Tenant.Id details.UserId = access.User.Id details.RegionServiceURLs = make(map[string]ServiceURLs, len(access.ServiceCatalog)) for _, service := range access.ServiceCatalog { for i, e := range service.Endpoints { endpointURLs, ok := details.RegionServiceURLs[e.Region] if !ok { endpointURLs = make(ServiceURLs) details.RegionServiceURLs[e.Region] = endpointURLs } endpointURLs[service.Type] = service.Endpoints[i].PublicURL } } return details, nil } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/identity/legacy.go000066400000000000000000000030661414605405700235450ustar00rootroot00000000000000package identity import ( "fmt" "io/ioutil" "net/http" goosehttp "gopkg.in/goose.v1/http" ) type Legacy struct { client *goosehttp.Client } func (l *Legacy) Auth(creds *Credentials) (*AuthDetails, error) { if l.client == nil { l.client = goosehttp.New() } request, err := http.NewRequest("GET", creds.URL, nil) if err != nil { return nil, err } request.Header.Set("X-Auth-User", creds.User) request.Header.Set("X-Auth-Key", creds.Secrets) response, err := l.client.Do(request) defer response.Body.Close() if err != nil { return nil, err } if response.StatusCode != http.StatusNoContent { content, _ := ioutil.ReadAll(response.Body) return nil, fmt.Errorf("Failed to Authenticate (code %d %s): %s", response.StatusCode, response.Status, content) } details := &AuthDetails{} details.Token = response.Header.Get("X-Auth-Token") if details.Token == "" { return nil, fmt.Errorf("Did not get valid Token from auth request") } details.RegionServiceURLs = make(map[string]ServiceURLs) serviceURLs := make(ServiceURLs) // Legacy authentication doesn't require a region so use "". details.RegionServiceURLs[""] = serviceURLs nova_url := response.Header.Get("X-Server-Management-Url") if nova_url == "" { return nil, fmt.Errorf("Did not get valid nova management URL from auth request") } serviceURLs["compute"] = nova_url swift_url := response.Header.Get("X-Storage-Url") if swift_url == "" { return nil, fmt.Errorf("Did not get valid swift management URL from auth request") } serviceURLs["object-store"] = swift_url return details, nil } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/identity/legacy_test.go000066400000000000000000000024211414605405700245760ustar00rootroot00000000000000package identity import ( gc "gopkg.in/check.v1" "gopkg.in/goose.v1/testing/httpsuite" "gopkg.in/goose.v1/testservices/identityservice" ) type LegacyTestSuite struct { httpsuite.HTTPSuite } var _ = gc.Suite(&LegacyTestSuite{}) func (s *LegacyTestSuite) TestAuthAgainstServer(c *gc.C) { service := identityservice.NewLegacy() s.Mux.Handle("/", service) userInfo := service.AddUser("joe-user", "secrets", "tenant", "default") service.SetManagementURL("http://management.test.invalid/url") var l Authenticator = &Legacy{} creds := Credentials{User: "joe-user", URL: s.Server.URL, Secrets: "secrets"} auth, err := l.Auth(&creds) c.Assert(err, gc.IsNil) c.Assert(auth.Token, gc.Equals, userInfo.Token) c.Assert( auth.RegionServiceURLs[""], gc.DeepEquals, ServiceURLs{"compute": "http://management.test.invalid/url/compute", "object-store": "http://management.test.invalid/url/object-store"}) } func (s *LegacyTestSuite) TestBadAuth(c *gc.C) { service := identityservice.NewLegacy() s.Mux.Handle("/", service) _ = service.AddUser("joe-user", "secrets", "tenant", "default") var l Authenticator = &Legacy{} creds := Credentials{User: "joe-user", URL: s.Server.URL, Secrets: "bad-secrets"} auth, err := l.Auth(&creds) c.Assert(err, gc.NotNil) c.Assert(auth, gc.IsNil) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/identity/live_test.go000066400000000000000000000017221414605405700242740ustar00rootroot00000000000000package identity_test import ( "net/url" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/client" "gopkg.in/goose.v1/identity" ) func registerOpenStackTests(cred *identity.Credentials, authMode identity.AuthMode) { gc.Suite(&LiveTests{ cred: cred, authMode: authMode, }) } type LiveTests struct { cred *identity.Credentials client client.AuthenticatingClient authMode identity.AuthMode } func (s *LiveTests) SetUpSuite(c *gc.C) { s.client = client.NewClient(s.cred, s.authMode, nil) } func (s *LiveTests) TearDownSuite(c *gc.C) { } func (s *LiveTests) SetUpTest(c *gc.C) { // noop, called by local test suite. } func (s *LiveTests) TearDownTest(c *gc.C) { // noop, called by local test suite. } func (s *LiveTests) TestAuth(c *gc.C) { err := s.client.Authenticate() c.Assert(err, gc.IsNil) serviceURL, err := s.client.MakeServiceURL("compute", "v2", []string{}) c.Assert(err, gc.IsNil) _, err = url.Parse(serviceURL) c.Assert(err, gc.IsNil) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/identity/local_test.go000066400000000000000000000040311414605405700244230ustar00rootroot00000000000000package identity_test import ( "net/url" "strings" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/identity" "gopkg.in/goose.v1/testservices/openstackservice" ) func registerLocalTests(authMode identity.AuthMode) { lt := LiveTests{authMode: authMode} gc.Suite(&localLiveSuite{LiveTests: lt}) } // localLiveSuite runs tests from LiveTests using a fake // nova server that runs within the test process itself. type localLiveSuite struct { LiveTests openstack *openstackservice.Openstack } func (s *localLiveSuite) SetUpSuite(c *gc.C) { c.Logf("Using identity and nova service test doubles") // Set up an Openstack service. s.cred = &identity.Credentials{ User: "fred", Secrets: "secret", Region: "zone1.some region", TenantName: "tenant", } var logMsg []string s.openstack, logMsg = openstackservice.New(s.cred, s.authMode, false) for _, msg := range logMsg { c.Logf(msg) } s.openstack.SetupHTTP(nil) if s.authMode == identity.AuthUserPassV3 { s.cred.URL = s.cred.URL + "/v3" } s.openstack.Identity.AddUser("fred", "secret", "tenant", "default") s.LiveTests.SetUpSuite(c) } func (s *localLiveSuite) TearDownSuite(c *gc.C) { s.LiveTests.TearDownSuite(c) s.openstack.Stop() } func (s *localLiveSuite) SetUpTest(c *gc.C) { s.LiveTests.SetUpTest(c) } func (s *localLiveSuite) TearDownTest(c *gc.C) { s.LiveTests.TearDownTest(c) } // Additional tests to be run against the service double only go here. func (s *localLiveSuite) TestProductStreamsEndpoint(c *gc.C) { err := s.client.Authenticate() c.Assert(err, gc.IsNil) serviceURL, err := s.client.MakeServiceURL("product-streams", "", nil) c.Assert(err, gc.IsNil) _, err = url.Parse(serviceURL) c.Assert(err, gc.IsNil) c.Assert(strings.HasSuffix(serviceURL, "/imagemetadata"), gc.Equals, true) } func (s *localLiveSuite) TestJujuToolsEndpoint(c *gc.C) { err := s.client.Authenticate() c.Assert(err, gc.IsNil) serviceURL, err := s.client.MakeServiceURL("juju-tools", "", nil) c.Assert(err, gc.IsNil) _, err = url.Parse(serviceURL) c.Assert(err, gc.IsNil) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/identity/setup_test.go000066400000000000000000000013011414605405700244660ustar00rootroot00000000000000package identity_test import ( "flag" "testing" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/identity" ) var live = flag.Bool("live", false, "Include live OpenStack (Canonistack) tests") var v3 = flag.Bool("v3", false, "Run keystone v3 tests instead of v2 (requires live flag)") func Test(t *testing.T) { if *live { cred, err := identity.CompleteCredentialsFromEnv() if err != nil { t.Fatalf("Error setting up test suite: %s", err.Error()) } if *v3 { registerOpenStackTests(cred, identity.AuthUserPassV3) } else { registerOpenStackTests(cred, identity.AuthUserPass) } } registerLocalTests(identity.AuthUserPassV3) registerLocalTests(identity.AuthUserPass) gc.TestingT(t) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/identity/userpass.go000066400000000000000000000014321414605405700241410ustar00rootroot00000000000000package identity import ( goosehttp "gopkg.in/goose.v1/http" ) type passwordCredentials struct { Username string `json:"username"` Password string `json:"password"` } type authRequest struct { PasswordCredentials passwordCredentials `json:"passwordCredentials"` TenantName string `json:"tenantName"` } type authWrapper struct { Auth authRequest `json:"auth"` } type UserPass struct { client *goosehttp.Client } func (u *UserPass) Auth(creds *Credentials) (*AuthDetails, error) { if u.client == nil { u.client = goosehttp.New() } auth := authWrapper{Auth: authRequest{ PasswordCredentials: passwordCredentials{ Username: creds.User, Password: creds.Secrets, }, TenantName: creds.TenantName}} return keystoneAuth(u.client, auth, creds.URL) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/identity/userpass_test.go000066400000000000000000000044301414605405700252010ustar00rootroot00000000000000package identity import ( gc "gopkg.in/check.v1" "gopkg.in/goose.v1/testing/httpsuite" "gopkg.in/goose.v1/testservices/identityservice" ) type UserPassTestSuite struct { httpsuite.HTTPSuite } var _ = gc.Suite(&UserPassTestSuite{}) func (s *UserPassTestSuite) TestAuthAgainstServer(c *gc.C) { service := identityservice.NewUserPass() service.SetupHTTP(s.Mux) userInfo := service.AddUser("joe-user", "secrets", "tenant", "default") var l Authenticator = &UserPass{} creds := Credentials{User: "joe-user", URL: s.Server.URL + "/tokens", Secrets: "secrets"} auth, err := l.Auth(&creds) c.Assert(err, gc.IsNil) c.Assert(auth.Token, gc.Equals, userInfo.Token) c.Assert(auth.TenantId, gc.Equals, userInfo.TenantId) } // Test that the region -> service endpoint map is correctly populated. func (s *UserPassTestSuite) TestRegionMatch(c *gc.C) { service := identityservice.NewUserPass() service.SetupHTTP(s.Mux) userInfo := service.AddUser("joe-user", "secrets", "tenant", "default") serviceDef := identityservice.V2Service{ Name: "swift", Type: "object-store", Endpoints: []identityservice.Endpoint{ {PublicURL: "http://swift", Region: "RegionOne"}, }} service.AddService(identityservice.Service{V2: serviceDef}) serviceDef = identityservice.V2Service{ Name: "nova", Type: "compute", Endpoints: []identityservice.Endpoint{ {PublicURL: "http://nova", Region: "zone1.RegionOne"}, }} service.AddService(identityservice.Service{V2: serviceDef}) serviceDef = identityservice.V2Service{ Name: "nova", Type: "compute", Endpoints: []identityservice.Endpoint{ {PublicURL: "http://nova2", Region: "zone2.RegionOne"}, }} service.AddService(identityservice.Service{V2: serviceDef}) creds := Credentials{ User: "joe-user", URL: s.Server.URL + "/tokens", Secrets: "secrets", Region: "zone1.RegionOne", } var l Authenticator = &UserPass{} auth, err := l.Auth(&creds) c.Assert(err, gc.IsNil) c.Assert(auth.RegionServiceURLs["RegionOne"]["object-store"], gc.Equals, "http://swift") c.Assert(auth.RegionServiceURLs["zone1.RegionOne"]["compute"], gc.Equals, "http://nova") c.Assert(auth.RegionServiceURLs["zone2.RegionOne"]["compute"], gc.Equals, "http://nova2") c.Assert(auth.Token, gc.Equals, userInfo.Token) c.Assert(auth.TenantId, gc.Equals, userInfo.TenantId) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/identity/v3userpass.go000066400000000000000000000124561414605405700244220ustar00rootroot00000000000000package identity import ( "fmt" "net/http" "time" goosehttp "gopkg.in/goose.v1/http" ) // v3AuthWrapper wraps the v3AuthRequest to perform v3 authentication. type v3AuthWrapper struct { Auth v3AuthRequest `json:"auth"` } // v3AuthRequest contains the authentication request. type v3AuthRequest struct { Identity v3AuthIdentity `json:"identity"` Scope *v3AuthScope `json:"scope,omitempty"` } // v3AuthIdentity contains the identity portion of an authentication // request. type v3AuthIdentity struct { Methods []string `json:"methods"` Password *v3AuthPassword `json:"password,omitempty"` Token *v3AuthToken `json:"token,omitempty"` } // v3AuthPassword contains a password authentication request. type v3AuthPassword struct { User v3AuthUser `json:"user"` } // v3AuthUser contains the user part of a password authentication // request.. type v3AuthUser struct { Domain *v3AuthDomain `json:"domain,omitempty"` ID string `json:"id,omitempty"` Name string `json:"name,omitempty"` Password string `json:"password"` } // v3AuthDomain contains a domain definition of an authentication // request. type v3AuthDomain struct { ID string `json:"id,omitempty"` Name string `json:"name,omitempty"` } // v3AuthToken contains the token to use for token authentication. type v3AuthToken struct { ID string `json:"id"` } // v3AuthScope contains the scope of the authentication request. type v3AuthScope struct { Domain *v3AuthDomain `json:"domain,omitempty"` Project *v3AuthProject `json:"project,omitempty"` } // v3AuthProject contains the project scope for the authentication // request. type v3AuthProject struct { Domain *v3AuthDomain `json:"domain,omitempty"` ID string `json:"id,omitempty"` Name string `json:"name,omitempty"` } // V3UserPass is an Authenticator that will perform username + password // authentication using the v3 protocol. type V3UserPass struct { client *goosehttp.Client } // Auth performs a v3 username + password authentication request using // the values supplied in creds. func (u *V3UserPass) Auth(creds *Credentials) (*AuthDetails, error) { if u.client == nil { u.client = goosehttp.New() } userDomain := creds.UserDomain if userDomain == "" { userDomain = "default" } projectDomain := creds.ProjectDomain if projectDomain == "" { projectDomain = "default" } auth := v3AuthWrapper{ Auth: v3AuthRequest{ Identity: v3AuthIdentity{ Methods: []string{"password"}, Password: &v3AuthPassword{ User: v3AuthUser{ Domain: &v3AuthDomain{ Name: userDomain, }, Name: creds.User, Password: creds.Secrets, }, }, }, }, } if creds.TenantName != "" { auth.Auth.Scope = &v3AuthScope{ Project: &v3AuthProject{ Domain: &v3AuthDomain{ Name: projectDomain, }, Name: creds.TenantName, }, } } if creds.Domain != "" { auth.Auth.Scope = &v3AuthScope{ Domain: &v3AuthDomain{ Name: creds.Domain, }, } } return v3KeystoneAuth(u.client, &auth, creds.URL) } type v3TokenWrapper struct { Token v3Token `json:"token"` } // v3Token represents the reponse token as described in: // http://developer.openstack.org/api-ref-identity-v3.html#authenticatePasswordScoped type v3Token struct { Expires time.Time `json:"expires_at"` Issued time.Time `json:"issued_at"` Methods []string `json:"methods"` Catalog []v3TokenCatalog `json:"catalog"` Project v3TokenProject `json:"project"` Domain v3TokenDomain `json:"domain"` User v3TokenUser `json:"user"` } type v3TokenCatalog struct { ID string `json:"id"` Type string `json:"type"` Name string `json:"name"` Endpoints []v3TokenEndpoint `json:"endpoints"` } type v3TokenEndpoint struct { ID string `json:"id"` RegionID string `json:"region_id"` URL string `json:"url"` Interface string `json:"interface"` } type v3TokenProject struct { ID string `json:"id"` Name string `json:"name"` Domain v3TokenDomain `json:"domain"` } type v3TokenUser struct { ID string `json:"id"` Name string `json:"name"` Domain v3TokenDomain `json:"domain"` } type v3TokenDomain struct { ID string `json:"id"` Name string `json:"name"` } // v3KeystoneAuth performs a v3 authentication request. func v3KeystoneAuth(c *goosehttp.Client, v interface{}, url string) (*AuthDetails, error) { var resp v3TokenWrapper req := goosehttp.RequestData{ ReqValue: v, RespValue: &resp, ExpectedStatus: []int{ http.StatusCreated, }, } if err := c.JsonRequest("POST", url, "", &req, nil); err != nil { return nil, fmt.Errorf("requesting token: %v", err) } tok := req.RespHeaders.Get("X-Subject-Token") if tok == "" { return nil, fmt.Errorf("authentication failed") } rsu := make(map[string]ServiceURLs, len(resp.Token.Catalog)) for _, s := range resp.Token.Catalog { for _, ep := range s.Endpoints { if ep.Interface != "public" { continue } su, ok := rsu[ep.RegionID] if !ok { su = make(ServiceURLs) rsu[ep.RegionID] = su } su[s.Type] = ep.URL } } return &AuthDetails{ Token: tok, TenantId: resp.Token.Project.ID, UserId: resp.Token.User.ID, Domain: resp.Token.Domain.Name, RegionServiceURLs: rsu, }, nil } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/identity/v3userpass_test.go000066400000000000000000000065421414605405700254600ustar00rootroot00000000000000package identity import ( gc "gopkg.in/check.v1" "gopkg.in/goose.v1/testing/httpsuite" "gopkg.in/goose.v1/testservices/identityservice" ) type V3UserPassTestSuite struct { httpsuite.HTTPSuite } var _ = gc.Suite(&V3UserPassTestSuite{}) func (s *V3UserPassTestSuite) TestAuthAgainstServer(c *gc.C) { service := identityservice.NewV3UserPass() service.SetupHTTP(s.Mux) userInfo := service.AddUser("joe-user", "secrets", "tenant", "default") var l Authenticator = &V3UserPass{} creds := Credentials{ User: "joe-user", URL: s.Server.URL + "/v3/auth/tokens", Secrets: "secrets", } auth, err := l.Auth(&creds) c.Assert(err, gc.IsNil) c.Assert(auth.Token, gc.Equals, userInfo.Token) } func (s *V3UserPassTestSuite) TestAuthToAProject(c *gc.C) { service := identityservice.NewV3UserPass() service.SetupHTTP(s.Mux) userInfo := service.AddUser("joe-user", "secrets", "tenant", "project-domain") var l Authenticator = &V3UserPass{} creds := Credentials{ User: "joe-user", URL: s.Server.URL + "/v3/auth/tokens", Secrets: "secrets", TenantName: "tenant", ProjectDomain: "project-domain", } auth, err := l.Auth(&creds) c.Assert(err, gc.IsNil) c.Assert(auth.Token, gc.Equals, userInfo.Token) c.Assert(auth.TenantId, gc.Equals, userInfo.TenantId) } func (s *V3UserPassTestSuite) TestAuthToADomain(c *gc.C) { service := identityservice.NewV3UserPass() service.SetupHTTP(s.Mux) userInfo := service.AddUser("joe-user", "secrets", "tenant", "domain") var l Authenticator = &V3UserPass{} creds := Credentials{ User: "joe-user", URL: s.Server.URL + "/v3/auth/tokens", Secrets: "secrets", TenantName: "tenant", Domain: "domain", } auth, err := l.Auth(&creds) c.Assert(err, gc.IsNil) c.Assert(auth.Token, gc.Equals, userInfo.Token) c.Assert(auth.Domain, gc.Equals, "domain") } func (s *V3UserPassTestSuite) TestAuthWithCatalog(c *gc.C) { service := identityservice.NewV3UserPass() service.SetupHTTP(s.Mux) userInfo := service.AddUser("joe-user", "secrets", "tenant", "default") serviceDef := identityservice.V3Service{ Name: "swift", Type: "object-store", Endpoints: identityservice.NewV3Endpoints("", "", "http://swift", "RegionOne"), } service.AddService(identityservice.Service{V3: serviceDef}) serviceDef = identityservice.V3Service{ Name: "nova", Type: "compute", Endpoints: identityservice.NewV3Endpoints("", "", "http://nova", "zone1.RegionOne"), } service.AddService(identityservice.Service{V3: serviceDef}) serviceDef = identityservice.V3Service{ Name: "nova", Type: "compute", Endpoints: identityservice.NewV3Endpoints("", "http://int.nova2", "http://nova2", "zone2.RegionOne"), } service.AddService(identityservice.Service{V3: serviceDef}) creds := Credentials{ User: "joe-user", URL: s.Server.URL + "/v3/auth/tokens", Secrets: "secrets", TenantName: "tenant", } var l Authenticator = &V3UserPass{} auth, err := l.Auth(&creds) c.Assert(err, gc.IsNil) c.Assert(auth.RegionServiceURLs["RegionOne"]["object-store"], gc.Equals, "http://swift") c.Assert(auth.RegionServiceURLs["zone1.RegionOne"]["compute"], gc.Equals, "http://nova") c.Assert(auth.RegionServiceURLs["zone2.RegionOne"]["compute"], gc.Equals, "http://nova2") c.Assert(auth.Token, gc.Equals, userInfo.Token) c.Assert(auth.TenantId, gc.Equals, userInfo.TenantId) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/logging/000077500000000000000000000000001414605405700215425ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/logging/logger.go000066400000000000000000000041631414605405700233540ustar00rootroot00000000000000// Copyright 2016 Canonical Ltd. // Licensed under the LGPLv3, see COPYING and COPYING.LESSER file for details. package logging import ( "log" "github.com/juju/loggo" ) // CompatLogger is a minimal logging interface that may be provided // when constructing a goose Client to log requests and responses, // retaining compatibility with the old *log.Logger that was // previously depended upon directly. // // TODO(axw) in goose.v2, drop this and use loggo.Logger directly. type CompatLogger interface { // Printf prints a log message. Arguments are handled // in the/ manner of fmt.Printf. Printf(format string, v ...interface{}) } // Logger is a logging interface that may be provided when constructing // a goose Client to log requests and responses. type Logger interface { Debugf(format string, v ...interface{}) Warningf(format string, v ...interface{}) } // LoggoLogger is a logger that may be provided when constructing // a goose Client to log requests and responses. Users must // provide a CompatLogger, which will be upgraded to Logger // if provided. type LoggoLogger struct { loggo.Logger } // Printf is part of the CompatLogger interface. func (l LoggoLogger) Printf(format string, v ...interface{}) { l.Debugf(format, v...) } // CompatLoggerAdapter is a type wrapping CompatLogger, implementing // the Logger interface. type CompatLoggerAdapter struct { CompatLogger } // Debugf is part of the Logger interface. func (a CompatLoggerAdapter) Debugf(format string, v ...interface{}) { a.Printf("DEBUG: "+format, v...) } // Warningf is part of the Logger interface. func (a CompatLoggerAdapter) Warningf(format string, v ...interface{}) { a.Printf("WARNING: "+format, v...) } // FromCompat takes a CompatLogger, and returns a Logger. This function // always returns a non-nil Logger; if the input is nil, then a no-op // Logger is returned. func FromCompat(in CompatLogger) Logger { if in == nil || in == (*log.Logger)(nil) { return CompatLoggerAdapter{nopLogger{}} } if l, ok := in.(Logger); ok { return l } return CompatLoggerAdapter{in} } type nopLogger struct{} func (nopLogger) Printf(string, ...interface{}) {} golang-gopkg-goose.v1-0.0~git20170406.3228e4f/neutron/000077500000000000000000000000001414605405700216065ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/neutron/live_test.go000066400000000000000000000213671414605405700241440ustar00rootroot00000000000000package neutron_test import ( gc "gopkg.in/check.v1" "net" "gopkg.in/goose.v1/client" "gopkg.in/goose.v1/identity" "gopkg.in/goose.v1/neutron" ) func registerOpenStackTests(cred *identity.Credentials) { gc.Suite(&LiveTests{ cred: cred, }) } type LiveTests struct { cred *identity.Credentials client client.AuthenticatingClient neutron *neutron.Client userId string tenantId string } func (s *LiveTests) SetUpSuite(c *gc.C) { s.client = client.NewClient(s.cred, identity.AuthUserPass, nil) s.neutron = neutron.New(s.client) } func (s *LiveTests) TearDownSuite(c *gc.C) { // noop, called by local test suite. } func (s *LiveTests) SetUpTest(c *gc.C) { // noop, called by local test suite. } func (s *LiveTests) TearDownTest(c *gc.C) { // noop, called by local test suite. } func (s *LiveTests) TestFloatingIPsV2(c *gc.C) { networks, err := s.neutron.ListNetworksV2() c.Assert(err, gc.IsNil) var netId string for _, net := range networks { if net.External == true { netId = net.Id break } } if netId == "" { c.Errorf("no valid network to create floating IP") } c.Assert(netId, gc.Not(gc.Equals), "") ip, err := s.neutron.AllocateFloatingIPV2(netId) c.Assert(err, gc.IsNil) defer s.neutron.DeleteFloatingIPV2(ip.Id) c.Assert(ip, gc.Not(gc.IsNil)) c.Check(ip.IP, gc.Not(gc.Equals), "") c.Check(ip.FixedIP, gc.Equals, "") c.Check(ip.Id, gc.Not(gc.Equals), "") c.Check(ip.FloatingNetworkId, gc.Not(gc.Equals), "") ips, err := s.neutron.ListFloatingIPsV2() c.Assert(err, gc.IsNil) if len(ips) < 1 { c.Errorf("no floating IPs found (expected at least 1)") } else { found := false for _, i := range ips { c.Check(i.IP, gc.Not(gc.Equals), "") if i.Id == ip.Id { c.Check(i.IP, gc.Equals, ip.IP) c.Check(i.FloatingNetworkId, gc.Equals, ip.FloatingNetworkId) found = true } } if !found { c.Errorf("expected to find added floating IP: %#v", ip) } fip, err := s.neutron.GetFloatingIPV2(ip.Id) c.Assert(err, gc.IsNil) c.Check(fip.Id, gc.Equals, ip.Id) c.Check(fip.IP, gc.Equals, ip.IP) c.Check(fip.FloatingNetworkId, gc.Equals, ip.FloatingNetworkId) } err = s.neutron.DeleteFloatingIPV2(ip.Id) c.Assert(err, gc.IsNil) _, err = s.neutron.GetFloatingIPV2(ip.Id) c.Assert(err, gc.Not(gc.IsNil)) } func (s *LiveTests) TestListNetworksV2(c *gc.C) { networks, err := s.neutron.ListNetworksV2() c.Assert(err, gc.IsNil) if len(networks) < 1 { c.Errorf("at least one neutron network is necessary for this tests") } for _, network := range networks { c.Check(network.Id, gc.Not(gc.Equals), "") c.Check(network.Name, gc.Not(gc.Equals), "") } firstNetwork := networks[0] foundNetwork, err := s.neutron.GetNetworkV2(firstNetwork.Id) c.Assert(err, gc.IsNil) c.Check(foundNetwork.Id, gc.Equals, firstNetwork.Id) c.Check(foundNetwork.Name, gc.Equals, firstNetwork.Name) } func (s *LiveTests) TestListNetworksV2WithFilters(c *gc.C) { filter := neutron.NewFilter() filter.Set(neutron.FilterRouterExternal, "true") networks, err := s.neutron.ListNetworksV2(filter) c.Assert(err, gc.IsNil) if len(networks) < 1 { c.Errorf("at least one neutron network is necessary for this tests") } for _, network := range networks { c.Check(network.Id, gc.Not(gc.Equals), "") c.Check(network.Name, gc.Not(gc.Equals), "") c.Check(network.External, gc.Equals, true) } firstNetwork := networks[0] foundNetwork, err := s.neutron.GetNetworkV2(firstNetwork.Id) c.Assert(err, gc.IsNil) c.Check(foundNetwork.Id, gc.Equals, firstNetwork.Id) c.Check(foundNetwork.Name, gc.Equals, firstNetwork.Name) } func (s *LiveTests) TestSubnetsV2(c *gc.C) { subnets, err := s.neutron.ListSubnetsV2() c.Assert(err, gc.IsNil) if len(subnets) < 1 { c.Errorf("at least one neutron subnet is necessary for this tests") } for _, subnet := range subnets { c.Check(subnet.Id, gc.Not(gc.Equals), "") c.Check(subnet.NetworkId, gc.Not(gc.Equals), "") c.Check(subnet.Name, gc.Not(gc.Equals), "") _, _, err := net.ParseCIDR(subnet.Cidr) c.Assert(err, gc.IsNil) } firstSubnet := subnets[0] foundSubnet, err := s.neutron.GetSubnetV2(firstSubnet.Id) c.Assert(err, gc.IsNil) c.Check(foundSubnet.Id, gc.Equals, firstSubnet.Id) c.Check(foundSubnet.NetworkId, gc.Equals, firstSubnet.NetworkId) c.Check(foundSubnet.Name, gc.Equals, firstSubnet.Name) } func (s *LiveTests) deleteSecurityGroup(id string, c *gc.C) { err := s.neutron.DeleteSecurityGroupV2(id) c.Assert(err, gc.IsNil) } func (s *LiveTests) TestSecurityGroupsV2(c *gc.C) { newSecGrp, err := s.neutron.CreateSecurityGroupV2("SecurityGroupTest", "Testing create security group") c.Assert(err, gc.IsNil) c.Assert(newSecGrp, gc.Not(gc.IsNil)) defer s.deleteSecurityGroup(newSecGrp.Id, c) secGrps, err := s.neutron.ListSecurityGroupsV2() c.Assert(err, gc.IsNil) c.Assert(secGrps, gc.Not(gc.HasLen), 0) var found bool for _, secGrp := range secGrps { c.Check(secGrp.Id, gc.Not(gc.Equals), "") c.Check(secGrp.Name, gc.Not(gc.Equals), "") c.Check(secGrp.Description, gc.Not(gc.Equals), "") c.Check(secGrp.TenantId, gc.Not(gc.Equals), "") // Is this the SecurityGroup we just created? if secGrp.Id == newSecGrp.Id { found = true } } if !found { c.Errorf("expected to find added security group %s", newSecGrp) } // Change the created SecurityGroup's name updatedSecGroup, err := s.neutron.UpdateSecurityGroupV2(newSecGrp.Id, "NameChanged", "") // Verify the name change foundSecGrps, err := s.neutron.SecurityGroupByNameV2(updatedSecGroup.Name) c.Assert(err, gc.IsNil) c.Assert(foundSecGrps, gc.Not(gc.HasLen), 0) found = false for _, secGrp := range foundSecGrps { if secGrp.Id == updatedSecGroup.Id { found = true break } } if !found { c.Errorf("expected to find added security group %s, when requested by name", updatedSecGroup.Name) } _, err = s.neutron.SecurityGroupByNameV2(newSecGrp.Name) c.Assert(err, gc.Not(gc.IsNil)) } func (s *LiveTests) TestSecurityGroupsByNameV2(c *gc.C) { // Create and find a SecurityGroup newSecGrp, err := s.neutron.CreateSecurityGroupV2("SecurityGroupTest", "Testing find security group by name") c.Assert(err, gc.IsNil) defer s.deleteSecurityGroup(newSecGrp.Id, c) c.Assert(newSecGrp, gc.Not(gc.IsNil)) foundSecGrps, err := s.neutron.SecurityGroupByNameV2(newSecGrp.Name) c.Assert(err, gc.IsNil) c.Assert(foundSecGrps, gc.HasLen, 1) if newSecGrp.Id != foundSecGrps[0].Id { c.Errorf("expected to find added security group %s, when requested by name", newSecGrp.Name) } // Try to find a SecurityGroup that doesn't exist errorSecGrps, err := s.neutron.SecurityGroupByNameV2("NonExistentGroup") c.Assert(err, gc.Not(gc.IsNil)) c.Assert(errorSecGrps, gc.HasLen, 0) // Create and find a SecurityGroup with spaces in the name newSecGrp2, err := s.neutron.CreateSecurityGroupV2("Security Group Test", "Testing find security group by name") c.Assert(err, gc.IsNil) defer s.deleteSecurityGroup(newSecGrp2.Id, c) c.Assert(newSecGrp2, gc.Not(gc.IsNil)) foundSecGrps2, err := s.neutron.SecurityGroupByNameV2(newSecGrp2.Name) c.Assert(err, gc.IsNil) c.Assert(foundSecGrps2, gc.HasLen, 1) if newSecGrp2.Id != foundSecGrps2[0].Id { c.Errorf("expected to find added security group %s, when requested by name", newSecGrp2.Name) } // Create a second SecurityGroup with the same name as one already created, // find both. newSecGrp3, err := s.neutron.CreateSecurityGroupV2(newSecGrp.Name, "Testing find security group by name, 2nd") c.Assert(err, gc.IsNil) defer s.deleteSecurityGroup(newSecGrp3.Id, c) c.Assert(newSecGrp3, gc.Not(gc.IsNil)) foundSecGrps3, err := s.neutron.SecurityGroupByNameV2(newSecGrp.Name) c.Assert(err, gc.IsNil) c.Assert(foundSecGrps3, gc.HasLen, 2) } func (s *LiveTests) TestSecurityGroupsRulesV2(c *gc.C) { newSecGrp, err := s.neutron.CreateSecurityGroupV2("SecurityGroupTestRules", "Testing create security group") c.Assert(err, gc.IsNil) defer s.deleteSecurityGroup(newSecGrp.Id, c) rule := neutron.RuleInfoV2{ ParentGroupId: newSecGrp.Id, RemoteIPPrefix: "0.0.0.0/0", IPProtocol: "icmp", Direction: "ingress", EthernetType: "IPv4", } newSecGrpRule, err := s.neutron.CreateSecurityGroupRuleV2(rule) c.Assert(err, gc.IsNil) c.Assert(newSecGrp.Id, gc.Equals, newSecGrpRule.ParentGroupId) c.Assert(*newSecGrpRule.IPProtocol, gc.Equals, rule.IPProtocol) c.Assert(newSecGrpRule.Direction, gc.Equals, rule.Direction) secGrps, err := s.neutron.SecurityGroupByNameV2(newSecGrp.Name) c.Assert(err, gc.IsNil) c.Assert(secGrps, gc.Not(gc.HasLen), 0) var found bool for _, secGrp := range secGrps { if secGrp.Id == newSecGrp.Id { for _, secGrpRule := range secGrp.Rules { if newSecGrpRule.Id == secGrpRule.Id { found = true } } } } if !found { c.Errorf("expected to find added security group rule %s", newSecGrpRule.Id) } err = s.neutron.DeleteSecurityGroupRuleV2(newSecGrpRule.Id) c.Assert(err, gc.IsNil) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/neutron/local_test.go000066400000000000000000000035061414605405700242720ustar00rootroot00000000000000package neutron_test import ( "log" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/client" "gopkg.in/goose.v1/identity" "gopkg.in/goose.v1/neutron" "gopkg.in/goose.v1/testservices" "gopkg.in/goose.v1/testservices/hook" "gopkg.in/goose.v1/testservices/openstackservice" ) func registerLocalTests() { gc.Suite(&localLiveSuite{}) } // localLiveSuite runs tests from LiveTests using a fake // neutron server that runs within the test process itself. type localLiveSuite struct { LiveTests openstack *openstackservice.Openstack noMoreIPs bool // If true, addFloatingIP will return ErrNoMoreFloatingIPs ipLimitExceeded bool // If true, addFloatingIP will return ErrIPLimitExceeded } func (s *localLiveSuite) SetUpSuite(c *gc.C) { c.Logf("Using identity and neutron service test doubles") // Set up an Openstack service. s.cred = &identity.Credentials{ User: "fred", Secrets: "secret", Region: "some region", TenantName: "tenant", } var logMsg []string s.openstack, logMsg = openstackservice.New(s.cred, identity.AuthUserPass, false) for _, msg := range logMsg { c.Logf(msg) } s.openstack.UseNeutronNetworking() s.openstack.SetupHTTP(nil) s.LiveTests.SetUpSuite(c) } func (s *localLiveSuite) TearDownSuite(c *gc.C) { s.LiveTests.TearDownSuite(c) s.openstack.Stop() } func (s *localLiveSuite) setupClient(c *gc.C, logger *log.Logger) *neutron.Client { client := client.NewClient(s.cred, identity.AuthUserPass, logger) //client.SetVersionDiscoveryEnabled(false) return neutron.New(client) } func (s *localLiveSuite) addFloatingIPHook(sc hook.ServiceControl) hook.ControlProcessor { return func(sc hook.ServiceControl, args ...interface{}) error { if s.noMoreIPs { return testservices.NoMoreFloatingIPs } else if s.ipLimitExceeded { return testservices.IPLimitExceeded } return nil } } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/neutron/neutron.go000066400000000000000000000367421414605405700236430ustar00rootroot00000000000000// goose/neutron - Go package to interact with OpenStack Network Service (Neutron) API V2.0. // See documentation at: // http://developer.openstack.org/api-ref/networking/v2/index.html package neutron import ( "fmt" "gopkg.in/goose.v1/client" "gopkg.in/goose.v1/errors" goosehttp "gopkg.in/goose.v1/http" "net/http" "net/url" ) const ( ApiFloatingIPsV2 = "floatingips" ApiNetworksV2 = "networks" ApiSubnetsV2 = "subnets" ApiSecurityGroupsV2 = "security-groups" ApiSecurityGroupRulesV2 = "security-group-rules" ) // Filter keys for Networks. // As of the Newton release of OpenStack, Network filter by subnet was not implemented const ( FilterRouterExternal = "router:external" // The router:external FilterNetwork = "name" // The network name. ) // NetworkV2 contains details about a labeled network type NetworkV2 struct { Id string `json:"id"` // UUID of the resource Name string // User-provided name for the network range SubnetIds []string `json:"subnets"` // an array of subnet UUIDs External bool `json:"router:external"` // is this network connected to an external router AvailabilityZones []string `json:"availability_zones"` TenantId string `json:"tenant_id"` } // SubnetV2 contains details about a labeled subnet type SubnetV2 struct { Id string `json:"id"` // UUID of the resource NetworkId string `json:"network_id"` // UUID of the related network Name string `json:"name"` // User-provided name for the subnet Cidr string `json:"cidr"` // IP range covered by the subnet AllocationPools []interface{} `json:"allocation_pools"` TenantId string `json:"tenant_id"` } // Client provides a means to access the OpenStack Network Service. type Client struct { client client.Client } // New creates a new Client. func New(client client.Client) *Client { return &Client{client} } // ---------------------------------------------------------------------------- // Filter builds filtering parameters to be used in an OpenStack query which supports // filtering. For example: // // filter := NewFilter() // filter.Set(neutron.FilterRouterExternal, "true") // resp, err := neutron.ListNetworks(filter) // // TODO(hml): copied from the nova package. However it should really be pulled out // and shared between goose pkgs, but we don't want to break compatibility or rev // the package at this time. // type Filter struct { v url.Values } // NewFilter creates a new Filter. func NewFilter() *Filter { return &Filter{make(url.Values)} } // Set sets a value in the filter. func (f *Filter) Set(filter, value string) { f.v.Set(filter, value) } // ---------------------------------------------------------------------------- // ListNetworksV2 gives details on available networks, zero or one Filters // accepted, any more will be ignored. // // TODO(hml): when this package revs to a new version, make this the same as other // methods with Filters. We don't want to break compatibility at this time or rev // the package at this time. func (c *Client) ListNetworksV2(filter ...*Filter) ([]NetworkV2, error) { var resp struct { Networks []NetworkV2 `json:"networks"` } var params *url.Values if len(filter) > 0 { params = &filter[0].v } requestData := goosehttp.RequestData{RespValue: &resp, Params: params} err := c.client.SendRequest(client.GET, "network", "v2.0", ApiNetworksV2, &requestData) if err != nil { return nil, errors.Newf(err, "failed to get list of networks") } return resp.Networks, nil } // GetNetworkV2 gives details on a specific network func (c *Client) GetNetworkV2(netID string) (*NetworkV2, error) { var resp struct { Network NetworkV2 `json:"network"` } url := fmt.Sprintf("%s/%s", ApiNetworksV2, netID) requestData := goosehttp.RequestData{RespValue: &resp} err := c.client.SendRequest(client.GET, "network", "v2.0", url, &requestData) if err != nil { return nil, errors.Newf(err, "failed to get network detail") } return &resp.Network, nil } // ListSubnetsV2 gives details on available subnets func (c *Client) ListSubnetsV2() ([]SubnetV2, error) { var resp struct { Subnets []SubnetV2 `json:"subnets"` } requestData := goosehttp.RequestData{RespValue: &resp} err := c.client.SendRequest(client.GET, "network", "v2.0", ApiSubnetsV2, &requestData) if err != nil { return nil, errors.Newf(err, "failed to get list of subnets") } return resp.Subnets, nil } // GetSubnetV2 gives details on a specific subnet func (c *Client) GetSubnetV2(subnetID string) (*SubnetV2, error) { var resp struct { Subnet SubnetV2 `json:"subnet"` } url := fmt.Sprintf("%s/%s", ApiSubnetsV2, subnetID) requestData := goosehttp.RequestData{RespValue: &resp} err := c.client.SendRequest(client.GET, "network", "v2.0", url, &requestData) if err != nil { return nil, errors.Newf(err, "failed to get subnet detail") } return &resp.Subnet, nil } // FloatingIPV2 contains details about a floating ip type FloatingIPV2 struct { // FixedIP holds the private IP address of the machine (when assigned) FixedIP string `json:"fixed_ip_address"` Id string `json:"id"` IP string `json:"floating_ip_address"` FloatingNetworkId string `json:"floating_network_id"` } // ListFloatingIPsV2 lists floating IP addresses associated with the tenant or account. func (c *Client) ListFloatingIPsV2() ([]FloatingIPV2, error) { var resp struct { FloatingIPV2s []FloatingIPV2 `json:"floatingips"` } requestData := goosehttp.RequestData{RespValue: &resp} err := c.client.SendRequest(client.GET, "network", "v2.0", ApiFloatingIPsV2, &requestData) if err != nil { return nil, errors.Newf(err, "failed to list floating ips") } return resp.FloatingIPV2s, nil } // GetFloatingIPV2 lists details of the floating IP address associated with specified id. func (c *Client) GetFloatingIPV2(ipId string) (*FloatingIPV2, error) { var resp struct { FloatingIPV2 FloatingIPV2 `json:"floatingip"` } url := fmt.Sprintf("%s/%s", ApiFloatingIPsV2, ipId) requestData := goosehttp.RequestData{RespValue: &resp} err := c.client.SendRequest(client.GET, "network", "v2.0", url, &requestData) if err != nil { return nil, errors.Newf(err, "failed to get floating ip %s details", ipId) } return &resp.FloatingIPV2, nil } // AllocateFloatingIPV2 allocates a new floating IP address in the given external network. func (c *Client) AllocateFloatingIPV2(floatingNetworkId string) (*FloatingIPV2, error) { var req struct { FloatingIPV2 struct { FloatingNetworkId string `json:"floating_network_id"` } `json:"floatingip"` } req.FloatingIPV2.FloatingNetworkId = floatingNetworkId var resp struct { FloatingIPV2 FloatingIPV2 `json:"floatingip"` } requestData := goosehttp.RequestData{ ReqValue: req, RespValue: &resp, ExpectedStatus: []int{http.StatusCreated}, } err := c.client.SendRequest(client.POST, "network", "v2.0", ApiFloatingIPsV2, &requestData) if err != nil { return nil, errors.Newf(err, "failed to allocate a floating ip") } return &resp.FloatingIPV2, nil } // DeleteFloatingIPV2 deallocates the floating IP address associated with the specified id. func (c *Client) DeleteFloatingIPV2(ipId string) error { url := fmt.Sprintf("%s/%s", ApiFloatingIPsV2, ipId) requestData := goosehttp.RequestData{ExpectedStatus: []int{http.StatusNoContent}} err := c.client.SendRequest(client.DELETE, "network", "v2.0", url, &requestData) if err != nil { err = errors.Newf(err, "failed to delete floating ip %s details", ipId) } return err } // SecurityGroupRuleV2 describes a rule of a security group. There are 2 // basic rule types: ingress and egress rules (see RuleInfo struct). type SecurityGroupRuleV2 struct { PortRangeMax *int `json:"port_range_max"` // Can be nil PortRangeMin *int `json:"port_range_min"` // Can be nil IPProtocol *string `json:"protocol"` // Can be nil, must be defined if PortRange is used ParentGroupId string `json:"security_group_id"` RemoteIPPrefix string `json:"remote_ip_prefix"` RemoteGroupID string `json:"remote_group_id"` EthernetType string `json:"ethertype"` Direction string `json:"direction"` // Required Id string `json:",omitempty"` TenantId string `json:"tenant_id,omitempty"` } // SecurityGroupV2 describes a single security group in OpenStack. type SecurityGroupV2 struct { Rules []SecurityGroupRuleV2 `json:"security_group_rules"` TenantId string `json:"tenant_id"` Id string `json:"id"` Name string `json:"name"` Description string `json:"description"` } // ListSecurityGroupsV2 lists IDs, names, and other details for all security groups. func (c *Client) ListSecurityGroupsV2() ([]SecurityGroupV2, error) { var resp struct { Groups []SecurityGroupV2 `json:"security_groups"` } requestData := goosehttp.RequestData{RespValue: &resp} err := c.client.SendRequest(client.GET, "network", "v2.0", ApiSecurityGroupsV2, &requestData) if err != nil { return nil, errors.Newf(err, "failed to list security groups") } return resp.Groups, nil } // SecurityGroupByNameV2 returns the named security group. // OpenStack now supports filtering with API calls. // More than one Security Group may be returned, as names are not unique // e.g. name=default func (c *Client) SecurityGroupByNameV2(name string) ([]SecurityGroupV2, error) { var resp struct { Groups []SecurityGroupV2 `json:"security_groups"` } url := fmt.Sprintf("%s?name=%s", ApiSecurityGroupsV2, url.QueryEscape(name)) requestData := goosehttp.RequestData{RespValue: &resp} err := c.client.SendRequest(client.GET, "network", "v2.0", url, &requestData) if err != nil { return nil, err } if len(resp.Groups) == 0 { return nil, errors.Newf(err, "failed to find security group with name: %s", name) } return resp.Groups, nil } // CreateSecurityGroupV2 creates a new security group. func (c *Client) CreateSecurityGroupV2(name, description string) (*SecurityGroupV2, error) { var req struct { SecurityGroupV2 struct { Name string `json:"name"` Description string `json:"description"` } `json:"security_group"` } req.SecurityGroupV2.Name = name req.SecurityGroupV2.Description = description var resp struct { SecurityGroup SecurityGroupV2 `json:"security_group"` } requestData := goosehttp.RequestData{ ReqValue: req, RespValue: &resp, ExpectedStatus: []int{http.StatusCreated}, } err := c.client.SendRequest(client.POST, "network", "v2.0", ApiSecurityGroupsV2, &requestData) if err != nil { return nil, errors.Newf(err, "failed to create a security group with name: %s", name) } return &resp.SecurityGroup, nil } // DeleteSecurityGroupV2 deletes the specified security group. func (c *Client) DeleteSecurityGroupV2(groupId string) error { url := fmt.Sprintf("%s/%s", ApiSecurityGroupsV2, groupId) requestData := goosehttp.RequestData{ExpectedStatus: []int{http.StatusNoContent}} err := c.client.SendRequest(client.DELETE, "network", "v2.0", url, &requestData) if err != nil { err = errors.Newf(err, "failed to delete security group with id: %s", groupId) } return err } // UpdateSecurityGroupV2 updates the name and description of the given group. func (c *Client) UpdateSecurityGroupV2(groupId, name, description string) (*SecurityGroupV2, error) { var req struct { SecurityGroupV2 struct { Name string `json:"name"` Description string `json:"description"` } `json:"security_group"` } req.SecurityGroupV2.Name = name req.SecurityGroupV2.Description = description var resp struct { SecurityGroup SecurityGroupV2 `json:"security_group"` } url := fmt.Sprintf("%s/%s", ApiSecurityGroupsV2, groupId) requestData := goosehttp.RequestData{ReqValue: req, RespValue: &resp, ExpectedStatus: []int{http.StatusOK}} err := c.client.SendRequest(client.PUT, "network", "v2.0", url, &requestData) if err != nil { return nil, errors.Newf(err, "failed to update security group with Id %s to name: %s", groupId, name) } return &resp.SecurityGroup, nil } // RuleInfoV2 allows the callers of CreateSecurityGroupRuleV2() to // create 2 types of security group rules: ingress rules and egress // rules. Security Groups are applied on neutron ports. // // Each tenant/project has a default security group with a rule // which allows intercommunication among hosts associated with the // default security group. As a result, all egress traffic and // intercommunication in the default group are allowed and all ingress // from outside of the default group is dropped by default (in the // default security group). // // If no ingress rule is defined, all inbound traffic is dropped. // If no egress rule is defined, all outbound traffic is dropped. // // For more information: // http://docs.openstack.org/developer/neutron/devref/security_group_api.html // https://wiki.openstack.org/wiki/Neutron/SecurityGroups // Neutron source: https://github.com/openstack/neutron.git type RuleInfoV2 struct { // Ingress or egress, which is the direction in which the metering // rule is applied. Required. Direction string `json:"direction"` // IPProtocol is optional, and if specified must be "tcp", "udp" or // "icmp" (in the case of icmp, both PortRangeMax and PortRangeMin should // be blank). IPProtocol string `json:"protocol,omitempty"` // The maximum port number in the range that is matched by the // security group rule. The port_range_min attribute constrains // the port_range_max attribute. If the protocol is ICMP, this // value must be an ICMP type. PortRangeMax int `json:"port_range_max,omitempty"` // The minimum port number in the range that is matched by the // security group rule. If the protocol is TCP or UDP, this value // must be less than or equal to the port_range_max attribute value. // If the protocol is ICMP, this value must be an ICMP type. PortRangeMin int `json:"port_range_min,omitempty"` EthernetType string `json:"ethertype,omitempty"` // Cidr for ICMP RemoteIPPrefix string `json:"remote_ip_prefix"` // ParentGroupId is always required and specifies the group to which // the rule is added. ParentGroupId string `json:"security_group_id"` RemoteGroupId string `json:"remote_group_id,omitempty"` } // CreateSecurityGroupRuleV2 creates a security group rule. It can either be an // ingress rule or group rule (see the description of SecurityGroupRuleV2). func (c *Client) CreateSecurityGroupRuleV2(ruleInfo RuleInfoV2) (*SecurityGroupRuleV2, error) { var req struct { SecurityGroupRule RuleInfoV2 `json:"security_group_rule"` } req.SecurityGroupRule = ruleInfo var resp struct { SecurityGroupRule SecurityGroupRuleV2 `json:"security_group_rule"` } requestData := goosehttp.RequestData{ReqValue: req, RespValue: &resp, ExpectedStatus: []int{http.StatusCreated}} err := c.client.SendRequest(client.POST, "network", "v2.0", ApiSecurityGroupRulesV2, &requestData) if err != nil { return nil, errors.Newf(err, "failed to create a rule for the security group with id: %v", ruleInfo.ParentGroupId) } return &resp.SecurityGroupRule, nil } // DeleteSecurityGroupRuleV2 deletes the specified security group rule. func (c *Client) DeleteSecurityGroupRuleV2(ruleId string) error { url := fmt.Sprintf("%s/%s", ApiSecurityGroupRulesV2, ruleId) requestData := goosehttp.RequestData{ExpectedStatus: []int{http.StatusNoContent}} err := c.client.SendRequest(client.DELETE, "network", "v2.0", url, &requestData) if err != nil { err = errors.Newf(err, "failed to delete security group rule with id: %s", ruleId) } return err } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/neutron/neutron_test.go000066400000000000000000000006511414605405700246700ustar00rootroot00000000000000package neutron_test import ( "flag" "testing" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/identity" ) var live = flag.Bool("live", false, "Include live OpenStack tests") func Test(t *testing.T) { if *live { cred, err := identity.CompleteCredentialsFromEnv() if err != nil { t.Fatalf("Error setting up test suite: %s", err.Error()) } registerOpenStackTests(cred) } registerLocalTests() gc.TestingT(t) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/nova/000077500000000000000000000000001414605405700210575ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/nova/export_test.go000066400000000000000000000001041414605405700237610ustar00rootroot00000000000000package nova func UseNumericIds(val bool) { useNumericIds = val } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/nova/json.go000066400000000000000000000166741414605405700223750ustar00rootroot00000000000000// JSON marshaling and unmarshaling support for Openstack compute data structures. // This package deals with the difference between the API and on-the-wire data types. // Differences include encoding entity IDs as string vs int, depending on the Openstack // variant used. // // The marshaling support is included primarily for use by the test doubles. It needs to be // included here since Go requires methods to implemented in the same package as their receiver. package nova import ( "encoding/json" "fmt" "strconv" ) const ( idTag = "id" instanceIdTag = "instance_id" groupIdTag = "group_id" parentGroupIdTag = "parent_group_id" ) var useNumericIds bool = false // convertId returns the id as either a string or an int depending on what // implementation of Openstack we are emulating. func convertId(id string) interface{} { if !useNumericIds { return id } result, err := strconv.Atoi(id) if err != nil { panic(err) } return result } // getIdAsStringPtr extracts the field with the specified tag from the json data // and returns it converted to a string pointer. func getIdAsStringPtr(b []byte, tag string) (*string, error) { var out map[string]interface{} if err := json.Unmarshal(b, &out); err != nil { return nil, err } val, ok := out[tag] if !ok || val == nil { return nil, nil } floatVal, ok := val.(float64) var strVal string if ok { strVal = fmt.Sprint(int(floatVal)) } else { strVal = fmt.Sprint(val) } return &strVal, nil } // getIdAsString extracts the field with the specified tag from the json data // and returns it converted to a string. func getIdAsString(b []byte, tag string) (string, error) { strPtr, err := getIdAsStringPtr(b, tag) if err != nil { return "", err } if strPtr == nil { return "", nil } return *strPtr, nil } // appendJSON marshals the given attribute value and appends it as an encoded value to the given json data. // The newly encode (attr, value) is inserted just before the closing "}" in the json data. func appendJSON(data []byte, attr string, value interface{}) ([]byte, error) { newData, err := json.Marshal(&value) if err != nil { return nil, err } strData := string(data) result := fmt.Sprintf(`%s, "%s":%s}`, strData[:len(strData)-1], attr, string(newData)) return []byte(result), nil } type jsonEntity Entity func (entity *Entity) UnmarshalJSON(b []byte) error { var je jsonEntity = jsonEntity(*entity) var err error if err = json.Unmarshal(b, &je); err != nil { return err } if je.Id, err = getIdAsString(b, idTag); err != nil { return err } *entity = Entity(je) return nil } func (entity Entity) MarshalJSON() ([]byte, error) { var je jsonEntity = jsonEntity(entity) data, err := json.Marshal(&je) if err != nil { return nil, err } id := convertId(entity.Id) return appendJSON(data, idTag, id) } type jsonFlavorDetail FlavorDetail func (flavorDetail *FlavorDetail) UnmarshalJSON(b []byte) error { var jfd jsonFlavorDetail = jsonFlavorDetail(*flavorDetail) var err error if err = json.Unmarshal(b, &jfd); err != nil { return err } if jfd.Id, err = getIdAsString(b, idTag); err != nil { return err } *flavorDetail = FlavorDetail(jfd) return nil } func (flavorDetail FlavorDetail) MarshalJSON() ([]byte, error) { var jfd jsonFlavorDetail = jsonFlavorDetail(flavorDetail) data, err := json.Marshal(&jfd) if err != nil { return nil, err } id := convertId(flavorDetail.Id) return appendJSON(data, idTag, id) } type jsonServerDetail ServerDetail func (serverDetail *ServerDetail) UnmarshalJSON(b []byte) error { var jsd jsonServerDetail = jsonServerDetail(*serverDetail) var err error if err = json.Unmarshal(b, &jsd); err != nil { return err } if jsd.Id, err = getIdAsString(b, idTag); err != nil { return err } *serverDetail = ServerDetail(jsd) return nil } func (serverDetail ServerDetail) MarshalJSON() ([]byte, error) { var jsd jsonServerDetail = jsonServerDetail(serverDetail) data, err := json.Marshal(&jsd) if err != nil { return nil, err } id := convertId(serverDetail.Id) return appendJSON(data, idTag, id) } type jsonFloatingIP FloatingIP func (floatingIP *FloatingIP) UnmarshalJSON(b []byte) error { var jfip jsonFloatingIP = jsonFloatingIP(*floatingIP) var err error if err = json.Unmarshal(b, &jfip); err != nil { return err } if instIdPtr, err := getIdAsStringPtr(b, instanceIdTag); err != nil { return err } else { jfip.InstanceId = instIdPtr } if jfip.Id, err = getIdAsString(b, idTag); err != nil { return err } *floatingIP = FloatingIP(jfip) return nil } func (floatingIP FloatingIP) MarshalJSON() ([]byte, error) { var jfip jsonFloatingIP = jsonFloatingIP(floatingIP) data, err := json.Marshal(&jfip) if err != nil { return nil, err } id := convertId(floatingIP.Id) data, err = appendJSON(data, idTag, id) if err != nil { return nil, err } if floatingIP.InstanceId == nil { return data, nil } instId := convertId(*floatingIP.InstanceId) return appendJSON(data, instanceIdTag, instId) } type jsonSecurityGroup SecurityGroup func (securityGroup *SecurityGroup) UnmarshalJSON(b []byte) error { var jsg jsonSecurityGroup = jsonSecurityGroup(*securityGroup) var err error if err = json.Unmarshal(b, &jsg); err != nil { return err } if jsg.Id, err = getIdAsString(b, idTag); err != nil { return err } *securityGroup = SecurityGroup(jsg) return nil } func (securityGroup SecurityGroup) MarshalJSON() ([]byte, error) { var jsg jsonSecurityGroup = jsonSecurityGroup(securityGroup) data, err := json.Marshal(&jsg) if err != nil { return nil, err } id := convertId(securityGroup.Id) return appendJSON(data, idTag, id) } type jsonSecurityGroupRule SecurityGroupRule func (securityGroupRule *SecurityGroupRule) UnmarshalJSON(b []byte) error { var jsgr jsonSecurityGroupRule = jsonSecurityGroupRule(*securityGroupRule) var err error if err = json.Unmarshal(b, &jsgr); err != nil { return err } if jsgr.Id, err = getIdAsString(b, idTag); err != nil { return err } if jsgr.ParentGroupId, err = getIdAsString(b, parentGroupIdTag); err != nil { return err } *securityGroupRule = SecurityGroupRule(jsgr) return nil } func (securityGroupRule SecurityGroupRule) MarshalJSON() ([]byte, error) { var jsgr jsonSecurityGroupRule = jsonSecurityGroupRule(securityGroupRule) data, err := json.Marshal(&jsgr) if err != nil { return nil, err } id := convertId(securityGroupRule.Id) data, err = appendJSON(data, idTag, id) if err != nil { return nil, err } if securityGroupRule.ParentGroupId == "" { return data, nil } id = convertId(securityGroupRule.ParentGroupId) return appendJSON(data, parentGroupIdTag, id) } type jsonRuleInfo RuleInfo func (ruleInfo *RuleInfo) UnmarshalJSON(b []byte) error { var jri jsonRuleInfo = jsonRuleInfo(*ruleInfo) var err error if err = json.Unmarshal(b, &jri); err != nil { return err } if jri.ParentGroupId, err = getIdAsString(b, parentGroupIdTag); err != nil { return err } if groupIdPtr, err := getIdAsStringPtr(b, groupIdTag); err != nil { return err } else { jri.GroupId = groupIdPtr } *ruleInfo = RuleInfo(jri) return nil } func (ruleInfo RuleInfo) MarshalJSON() ([]byte, error) { var jri jsonRuleInfo = jsonRuleInfo(ruleInfo) data, err := json.Marshal(&jri) if err != nil { return nil, err } id := convertId(ruleInfo.ParentGroupId) data, err = appendJSON(data, parentGroupIdTag, id) if err != nil { return nil, err } if ruleInfo.GroupId == nil { return data, nil } id = convertId(*ruleInfo.GroupId) return appendJSON(data, groupIdTag, id) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/nova/json_test.go000066400000000000000000000044511414605405700234220ustar00rootroot00000000000000package nova_test import ( "encoding/json" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/nova" ) type JsonSuite struct { } var _ = gc.Suite(&JsonSuite{}) func (s *JsonSuite) SetUpSuite(c *gc.C) { nova.UseNumericIds(true) } func (s *JsonSuite) assertMarshallRoundtrip(c *gc.C, value interface{}, unmarshalled interface{}) { data, err := json.Marshal(value) c.Assert(err, gc.IsNil) err = json.Unmarshal(data, &unmarshalled) c.Assert(err, gc.IsNil) c.Assert(unmarshalled, gc.DeepEquals, value) } // The following tests all check that unmarshalling of Ids with values > 1E6 // works properly. func (s *JsonSuite) TestMarshallEntityLargeIntId(c *gc.C) { entity := nova.Entity{Id: "2000000", Name: "test"} var unmarshalled nova.Entity s.assertMarshallRoundtrip(c, &entity, &unmarshalled) } func (s *JsonSuite) TestMarshallFlavorDetailLargeIntId(c *gc.C) { fd := nova.FlavorDetail{Id: "2000000", Name: "test"} var unmarshalled nova.FlavorDetail s.assertMarshallRoundtrip(c, &fd, &unmarshalled) } func (s *JsonSuite) TestMarshallServerDetailLargeIntId(c *gc.C) { fd := nova.Entity{Id: "2000000", Name: "test"} im := nova.Entity{Id: "2000000", Name: "test"} sd := nova.ServerDetail{Id: "2000000", Name: "test", Flavor: fd, Image: im} var unmarshalled nova.ServerDetail s.assertMarshallRoundtrip(c, &sd, &unmarshalled) } func (s *JsonSuite) TestMarshallFloatingIPLargeIntId(c *gc.C) { id := "3000000" fip := nova.FloatingIP{Id: "2000000", InstanceId: &id} var unmarshalled nova.FloatingIP s.assertMarshallRoundtrip(c, &fip, &unmarshalled) } func (s *JsonSuite) TestUnmarshallFloatingIPNilStrings(c *gc.C) { var fip nova.FloatingIP data := []byte(`{"instance_id": null, "ip": "10.1.1.1", "fixed_ip": null, "id": "12345", "pool": "Ext-Net"}`) err := json.Unmarshal(data, &fip) c.Assert(err, gc.IsNil) expected := nova.FloatingIP{ Id: "12345", IP: "10.1.1.1", Pool: "Ext-Net", FixedIP: nil, InstanceId: nil, } c.Assert(fip, gc.DeepEquals, expected) } func (s *JsonSuite) TestUnmarshallRuleInfoNilStrings(c *gc.C) { var ri nova.RuleInfo data := []byte(`{"group_id": null, "parent_group_id": "12345"}`) err := json.Unmarshal(data, &ri) c.Assert(err, gc.IsNil) expected := nova.RuleInfo{ GroupId: nil, ParentGroupId: "12345", } c.Assert(ri, gc.DeepEquals, expected) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/nova/live_test.go000066400000000000000000000454131414605405700234130ustar00rootroot00000000000000package nova_test import ( "bytes" "fmt" "log" "strings" "time" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/client" "gopkg.in/goose.v1/errors" "gopkg.in/goose.v1/identity" "gopkg.in/goose.v1/nova" ) const ( // A made up name we use for the test server instance. testImageName = "nova_test_server" ) func registerOpenStackTests(cred *identity.Credentials, testImageDetails imageDetails) { gc.Suite(&LiveTests{ cred: cred, testImageId: testImageDetails.imageId, testFlavor: testImageDetails.flavor, vendor: testImageDetails.vendor, }) } type LiveTests struct { cred *identity.Credentials client client.AuthenticatingClient nova *nova.Client testServer *nova.Entity userId string tenantId string testImageId string testFlavor string testFlavorId string testAvailabilityZone string vendor string useNumericIds bool } func (s *LiveTests) SetUpSuite(c *gc.C) { s.client = client.NewClient(s.cred, identity.AuthUserPass, nil) s.nova = nova.New(s.client) var err error s.testFlavorId, err = s.findFlavorId(s.testFlavor) c.Assert(err, gc.IsNil) s.testServer, err = s.createInstance(testImageName) c.Assert(err, gc.IsNil) s.waitTestServerToStart(c) // These will not be filled in until a client has authorised which will happen creating the instance above. s.userId = s.client.UserId() s.tenantId = s.client.TenantId() } func (s *LiveTests) findFlavorId(flavorName string) (string, error) { flavors, err := s.nova.ListFlavors() if err != nil { return "", err } var flavorId string for _, flavor := range flavors { if flavor.Name == flavorName { flavorId = flavor.Id break } } if flavorId == "" { return "", fmt.Errorf("No such flavor %s", flavorName) } return flavorId, nil } func (s *LiveTests) TearDownSuite(c *gc.C) { if s.testServer != nil { err := s.nova.DeleteServer(s.testServer.Id) c.Check(err, gc.IsNil) } } func (s *LiveTests) SetUpTest(c *gc.C) { // noop, called by local test suite. } func (s *LiveTests) TearDownTest(c *gc.C) { // noop, called by local test suite. } func (s *LiveTests) createInstance(name string) (instance *nova.Entity, err error) { opts := nova.RunServerOpts{ Name: name, FlavorId: s.testFlavorId, ImageId: s.testImageId, AvailabilityZone: s.testAvailabilityZone, UserData: nil, } instance, err = s.nova.RunServer(opts) if err != nil { return nil, err } return instance, nil } // Assert that the server record matches the details of the test server image. func (s *LiveTests) assertServerDetails(c *gc.C, sr *nova.ServerDetail) { c.Check(sr.Id, gc.Equals, s.testServer.Id) c.Check(sr.Name, gc.Equals, testImageName) c.Check(sr.Flavor.Id, gc.Equals, s.testFlavorId) c.Check(sr.Image.Id, gc.Equals, s.testImageId) if s.testAvailabilityZone != "" { c.Check(sr.AvailabilityZone, gc.Equals, s.testAvailabilityZone) } } func (s *LiveTests) TestListFlavors(c *gc.C) { flavors, err := s.nova.ListFlavors() c.Assert(err, gc.IsNil) if len(flavors) < 1 { c.Fatalf("no flavors to list") } for _, f := range flavors { c.Check(f.Id, gc.Not(gc.Equals), "") c.Check(f.Name, gc.Not(gc.Equals), "") for _, l := range f.Links { c.Check(l.Href, gc.Matches, "https?://.*") c.Check(l.Rel, gc.Matches, "self|bookmark") } } } func (s *LiveTests) TestListFlavorsDetail(c *gc.C) { flavors, err := s.nova.ListFlavorsDetail() c.Assert(err, gc.IsNil) if len(flavors) < 1 { c.Fatalf("no flavors (details) to list") } for _, f := range flavors { c.Check(f.Name, gc.Not(gc.Equals), "") c.Check(f.Id, gc.Not(gc.Equals), "") if f.RAM < 0 || f.VCPUs < 0 || f.Disk < 0 { c.Fatalf("invalid flavor found: %#v", f) } } } func (s *LiveTests) TestListServers(c *gc.C) { servers, err := s.nova.ListServers(nil) c.Assert(err, gc.IsNil) foundTest := false for _, sr := range servers { c.Check(sr.Id, gc.Not(gc.Equals), "") c.Check(sr.Name, gc.Not(gc.Equals), "") if sr.Id == s.testServer.Id { c.Check(sr.Name, gc.Equals, testImageName) foundTest = true } for _, l := range sr.Links { c.Check(l.Href, gc.Matches, "https?://.*") c.Check(l.Rel, gc.Matches, "self|bookmark") } } if !foundTest { c.Fatalf("test server (%s) not found in server list", s.testServer.Id) } } func (s *LiveTests) TestListServersWithFilter(c *gc.C) { inst, err := s.createInstance("filtered_server") c.Assert(err, gc.IsNil) defer s.nova.DeleteServer(inst.Id) filter := nova.NewFilter() filter.Set(nova.FilterServer, "filtered_server") servers, err := s.nova.ListServers(filter) c.Assert(err, gc.IsNil) found := false for _, sr := range servers { if sr.Id == inst.Id { c.Assert(sr.Name, gc.Equals, "filtered_server") found = true } } if !found { c.Fatalf("server (%s) not found in filtered server list %v", inst.Id, servers) } } func (s *LiveTests) TestListServersDetail(c *gc.C) { servers, err := s.nova.ListServersDetail(nil) c.Assert(err, gc.IsNil) if len(servers) < 1 { c.Fatalf("no servers to list (expected at least 1)") } foundTest := false for _, sr := range servers { // not checking for Addresses, because it could be missing c.Check(sr.Created, gc.Matches, `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*`) c.Check(sr.Updated, gc.Matches, `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*`) c.Check(sr.Id, gc.Not(gc.Equals), "") c.Check(sr.HostId, gc.Not(gc.Equals), "") c.Check(sr.TenantId, gc.Equals, s.tenantId) c.Check(sr.UserId, gc.Equals, s.userId) c.Check(sr.Status, gc.Not(gc.Equals), "") c.Check(sr.Name, gc.Not(gc.Equals), "") if sr.Id == s.testServer.Id { s.assertServerDetails(c, &sr) foundTest = true } for _, l := range sr.Links { c.Check(l.Href, gc.Matches, "https?://.*") c.Check(l.Rel, gc.Matches, "self|bookmark") } c.Check(sr.Flavor.Id, gc.Not(gc.Equals), "") for _, f := range sr.Flavor.Links { c.Check(f.Href, gc.Matches, "https?://.*") c.Check(f.Rel, gc.Matches, "self|bookmark") } c.Check(sr.Image.Id, gc.Not(gc.Equals), "") for _, i := range sr.Image.Links { c.Check(i.Href, gc.Matches, "https?://.*") c.Check(i.Rel, gc.Matches, "self|bookmark") } } if !foundTest { c.Fatalf("test server (%s) not found in server list (details)", s.testServer.Id) } } func (s *LiveTests) TestListServersDetailWithFilter(c *gc.C) { inst, err := s.createInstance("filtered_server") c.Assert(err, gc.IsNil) defer s.nova.DeleteServer(inst.Id) filter := nova.NewFilter() filter.Set(nova.FilterServer, "filtered_server") servers, err := s.nova.ListServersDetail(filter) c.Assert(err, gc.IsNil) found := false for _, sr := range servers { if sr.Id == inst.Id { c.Assert(sr.Name, gc.Equals, "filtered_server") found = true } } if !found { c.Fatalf("server (%s) not found in filtered server details %v", inst.Id, servers) } } func (s *LiveTests) TestListSecurityGroups(c *gc.C) { groups, err := s.nova.ListSecurityGroups() c.Assert(err, gc.IsNil) if len(groups) < 1 { c.Fatalf("no security groups found (expected at least 1)") } for _, g := range groups { c.Check(g.TenantId, gc.Equals, s.tenantId) c.Check(g.Name, gc.Not(gc.Equals), "") c.Check(g.Description, gc.Not(gc.Equals), "") c.Check(g.Rules, gc.NotNil) } } func (s *LiveTests) TestCreateAndDeleteSecurityGroup(c *gc.C) { group, err := s.nova.CreateSecurityGroup("test_secgroup", "test_desc") c.Assert(err, gc.IsNil) c.Check(group.Name, gc.Equals, "test_secgroup") c.Check(group.Description, gc.Equals, "test_desc") groups, err := s.nova.ListSecurityGroups() found := false for _, g := range groups { if g.Id == group.Id { found = true break } } if found { err = s.nova.DeleteSecurityGroup(group.Id) c.Check(err, gc.IsNil) } else { c.Fatalf("test security group (%d) not found", group.Id) } } func (s *LiveTests) TestUpdateSecurityGroup(c *gc.C) { group, err := s.nova.CreateSecurityGroup("test_secgroup", "test_desc") c.Assert(err, gc.IsNil) c.Check(group.Name, gc.Equals, "test_secgroup") c.Check(group.Description, gc.Equals, "test_desc") groupUpdated, err := s.nova.UpdateSecurityGroup(group.Id, "test_secgroup_new", "test_desc_new") c.Assert(err, gc.IsNil) c.Check(groupUpdated.Name, gc.Equals, "test_secgroup_new") c.Check(groupUpdated.Description, gc.Equals, "test_desc_new") groups, err := s.nova.ListSecurityGroups() found := false for _, g := range groups { if g.Id == group.Id { found = true c.Assert(g.Name, gc.Equals, "test_secgroup_new") c.Assert(g.Description, gc.Equals, "test_desc_new") break } } if found { err = s.nova.DeleteSecurityGroup(group.Id) c.Check(err, gc.IsNil) } else { c.Fatalf("test security group (%d) not found", group.Id) } } func (s *LiveTests) TestDuplicateSecurityGroupError(c *gc.C) { group, err := s.nova.CreateSecurityGroup("test_dupgroup", "test_desc") c.Assert(err, gc.IsNil) defer s.nova.DeleteSecurityGroup(group.Id) group, err = s.nova.CreateSecurityGroup("test_dupgroup", "test_desc") c.Assert(errors.IsDuplicateValue(err), gc.Equals, true) } func (s *LiveTests) TestCreateAndDeleteSecurityGroupRules(c *gc.C) { group1, err := s.nova.CreateSecurityGroup("test_secgroup1", "test_desc") c.Assert(err, gc.IsNil) group2, err := s.nova.CreateSecurityGroup("test_secgroup2", "test_desc") c.Assert(err, gc.IsNil) // First type of rule - port range + protocol ri := nova.RuleInfo{ IPProtocol: "tcp", FromPort: 1234, ToPort: 4321, Cidr: "10.0.0.0/8", ParentGroupId: group1.Id, } rule, err := s.nova.CreateSecurityGroupRule(ri) c.Assert(err, gc.IsNil) c.Check(*rule.FromPort, gc.Equals, 1234) c.Check(*rule.ToPort, gc.Equals, 4321) c.Check(rule.ParentGroupId, gc.Equals, group1.Id) c.Check(*rule.IPProtocol, gc.Equals, "tcp") c.Check(rule.Group, gc.Equals, nova.SecurityGroupRef{}) err = s.nova.DeleteSecurityGroupRule(rule.Id) c.Check(err, gc.IsNil) // Second type of rule - inherited from another group ri = nova.RuleInfo{ GroupId: &group2.Id, ParentGroupId: group1.Id, } rule, err = s.nova.CreateSecurityGroupRule(ri) c.Assert(err, gc.IsNil) c.Check(rule.ParentGroupId, gc.Equals, group1.Id) c.Check(rule.Group, gc.NotNil) c.Check(rule.Group.TenantId, gc.Equals, s.tenantId) c.Check(rule.Group.Name, gc.Equals, "test_secgroup2") err = s.nova.DeleteSecurityGroupRule(rule.Id) c.Check(err, gc.IsNil) err = s.nova.DeleteSecurityGroup(group1.Id) c.Check(err, gc.IsNil) err = s.nova.DeleteSecurityGroup(group2.Id) c.Check(err, gc.IsNil) } func (s *LiveTests) TestGetServer(c *gc.C) { server, err := s.nova.GetServer(s.testServer.Id) c.Assert(err, gc.IsNil) s.assertServerDetails(c, server) } func (s *LiveTests) waitTestServerToStart(c *gc.C) { // Wait until the test server is actually running c.Logf("waiting the test server %s to start...", s.testServer.Id) for { server, err := s.nova.GetServer(s.testServer.Id) c.Assert(err, gc.IsNil) if server.Status == nova.StatusActive { break } // We dont' want to flood the connection while polling the server waiting for it to start. c.Logf("server has status %s, waiting 10 seconds before polling again...", server.Status) time.Sleep(10 * time.Second) } c.Logf("started") } func (s *LiveTests) TestServerAddGetRemoveSecurityGroup(c *gc.C) { group, err := s.nova.CreateSecurityGroup("test_server_secgroup", "test desc") if err != nil { c.Assert(errors.IsDuplicateValue(err), gc.Equals, true) group, err = s.nova.SecurityGroupByName("test_server_secgroup") c.Assert(err, gc.IsNil) } s.waitTestServerToStart(c) err = s.nova.AddServerSecurityGroup(s.testServer.Id, group.Name) c.Assert(err, gc.IsNil) groups, err := s.nova.GetServerSecurityGroups(s.testServer.Id) c.Assert(err, gc.IsNil) found := false for _, g := range groups { if g.Id == group.Id || g.Name == group.Name { found = true break } } err = s.nova.RemoveServerSecurityGroup(s.testServer.Id, group.Name) c.Check(err, gc.IsNil) err = s.nova.DeleteSecurityGroup(group.Id) c.Assert(err, gc.IsNil) if !found { c.Fail() } } func (s *LiveTests) TestFloatingIPs(c *gc.C) { ip, err := s.nova.AllocateFloatingIP() c.Assert(err, gc.IsNil) defer s.nova.DeleteFloatingIP(ip.Id) c.Check(ip.IP, gc.Not(gc.Equals), "") c.Check(ip.FixedIP, gc.IsNil) c.Check(ip.InstanceId, gc.IsNil) ips, err := s.nova.ListFloatingIPs() c.Assert(err, gc.IsNil) if len(ips) < 1 { c.Errorf("no floating IPs found (expected at least 1)") } else { found := false for _, i := range ips { c.Check(i.IP, gc.Not(gc.Equals), "") if i.Id == ip.Id { c.Check(i.IP, gc.Equals, ip.IP) c.Check(i.Pool, gc.Equals, ip.Pool) found = true } } if !found { c.Errorf("expected to find added floating IP: %#v", ip) } fip, err := s.nova.GetFloatingIP(ip.Id) c.Assert(err, gc.IsNil) c.Check(fip.Id, gc.Equals, ip.Id) c.Check(fip.IP, gc.Equals, ip.IP) c.Check(fip.Pool, gc.Equals, ip.Pool) } } func (s *LiveTests) TestServerFloatingIPs(c *gc.C) { ip, err := s.nova.AllocateFloatingIP() c.Assert(err, gc.IsNil) defer s.nova.DeleteFloatingIP(ip.Id) c.Check(ip.IP, gc.Matches, `\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}`) s.waitTestServerToStart(c) err = s.nova.AddServerFloatingIP(s.testServer.Id, ip.IP) c.Assert(err, gc.IsNil) // TODO (wallyworld) - 2013-02-11 bug=1121666 // where we are creating a real server, test that the IP address created above // can be used to connect to the server defer s.nova.RemoveServerFloatingIP(s.testServer.Id, ip.IP) fip, err := s.nova.GetFloatingIP(ip.Id) c.Assert(err, gc.IsNil) c.Check(*fip.FixedIP, gc.Matches, `\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}`) c.Check(*fip.InstanceId, gc.Equals, s.testServer.Id) err = s.nova.RemoveServerFloatingIP(s.testServer.Id, ip.IP) c.Check(err, gc.IsNil) fip, err = s.nova.GetFloatingIP(ip.Id) c.Assert(err, gc.IsNil) c.Check(fip.FixedIP, gc.IsNil) c.Check(fip.InstanceId, gc.IsNil) } // TestRateLimitRetry checks that when we make too many requests and receive a Retry-After response, the retry // occurs and the request ultimately succeeds. func (s *LiveTests) TestRateLimitRetry(c *gc.C) { if s.vendor != "canonistack" { c.Skip("TestRateLimitRetry is only run for Canonistack") } // Capture the logged output so we can check for retry messages. var logout bytes.Buffer logger := log.New(&logout, "", log.LstdFlags) client := client.NewClient(s.cred, identity.AuthUserPass, logger) novaClient := nova.New(client) // Delete the artifact if it already exists. testGroup, err := novaClient.SecurityGroupByName("test_group") if err != nil { c.Assert(errors.IsNotFound(err), gc.Equals, true) } else { novaClient.DeleteSecurityGroup(testGroup.Id) c.Assert(err, gc.IsNil) } // Create some artifacts a number of times in succession and ensure each time is successful, // even with retries being required. As soon as we see a retry message, the test has passed // and we exit. for i := 0; i < 50; i++ { testGroup, err = novaClient.CreateSecurityGroup("test_group", "test") c.Assert(err, gc.IsNil) novaClient.DeleteSecurityGroup(testGroup.Id) c.Assert(err, gc.IsNil) output := logout.String() if strings.Contains(output, "Too many requests, retrying in") == true { return } } // No retry message logged so test has failed. c.Fail() } func (s *LiveTests) TestRegexpInstanceFilters(c *gc.C) { serverNames := []string{ "foobar123", "foo123baz", "123barbaz", } for _, name := range serverNames { inst, err := s.createInstance(name) c.Assert(err, gc.IsNil) defer s.nova.DeleteServer(inst.Id) } filter := nova.NewFilter() filter.Set(nova.FilterServer, `foo.*baz`) servers, err := s.nova.ListServersDetail(filter) c.Assert(err, gc.IsNil) c.Assert(servers, gc.HasLen, 1) c.Assert(servers[0].Name, gc.Equals, serverNames[1]) filter.Set(nova.FilterServer, `[0-9]+[a-z]+`) servers, err = s.nova.ListServersDetail(filter) c.Assert(err, gc.IsNil) c.Assert(servers, gc.HasLen, 2) if servers[0].Name != serverNames[1] { servers[0], servers[1] = servers[1], servers[0] } c.Assert(servers[0].Name, gc.Equals, serverNames[1]) c.Assert(servers[1].Name, gc.Equals, serverNames[2]) } func (s *LiveTests) TestListNetworks(c *gc.C) { networks, err := s.nova.ListNetworks() c.Assert(err, gc.IsNil) for _, network := range networks { c.Check(network.Id, gc.Not(gc.Equals), "") c.Check(network.Label, gc.Not(gc.Equals), "") c.Assert(network.Cidr, gc.Matches, `\d{1,3}(\.+\d{1,3}){3}\/\d+`) } } func (s *LiveTests) runServerAvailabilityZone(zone string) (*nova.Entity, error) { old := s.testAvailabilityZone defer func() { s.testAvailabilityZone = old }() s.testAvailabilityZone = zone return s.createInstance(testImageName) } func (s *LiveTests) TestRunServerUnknownAvailabilityZone(c *gc.C) { _, err := s.runServerAvailabilityZone("something_that_will_never_exist") c.Assert(err, gc.ErrorMatches, "(.|\n)*The requested availability zone is not available(.|\n)*") } func (s *LiveTests) TestUpdateServerName(c *gc.C) { entity, err := s.nova.RunServer(nova.RunServerOpts{ Name: "oldName", FlavorId: s.testFlavorId, ImageId: s.testImageId, AvailabilityZone: s.testAvailabilityZone, Metadata: map[string]string{}, }) c.Assert(err, gc.IsNil) defer s.nova.DeleteServer(entity.Id) newEntity, err := s.nova.UpdateServerName(entity.Id, "newName") c.Assert(err, gc.IsNil) c.Assert(newEntity.Name, gc.Equals, "newName") server, err := s.nova.GetServer(entity.Id) c.Assert(err, gc.IsNil) c.Assert(server.Name, gc.Equals, "newName") } func (s *LiveTests) TestInstanceMetadata(c *gc.C) { metadata := map[string]string{"my-key": "my-value"} entity, err := s.nova.RunServer(nova.RunServerOpts{ Name: "inst-metadata", FlavorId: s.testFlavorId, ImageId: s.testImageId, AvailabilityZone: s.testAvailabilityZone, Metadata: metadata, }) c.Assert(err, gc.IsNil) defer s.nova.DeleteServer(entity.Id) server, err := s.nova.GetServer(entity.Id) c.Assert(err, gc.IsNil) // nova may have added metadata as well; // delete it before comparing. for k := range server.Metadata { if _, ok := metadata[k]; !ok { delete(server.Metadata, k) } } c.Assert(server.Metadata, gc.DeepEquals, metadata) } func (s *LiveTests) TestSetServerMetadata(c *gc.C) { entity, err := s.nova.RunServer(nova.RunServerOpts{ Name: "inst-metadata", FlavorId: s.testFlavorId, ImageId: s.testImageId, AvailabilityZone: s.testAvailabilityZone, }) c.Assert(err, gc.IsNil) defer s.nova.DeleteServer(entity.Id) for _, metadata := range []map[string]string{{ "k1": "v1", }, { "k1": "v1.replacement", "k2": "v2", }, { // Check that missing keys get left alone. "k2": "v2.replacement", }} { err = s.nova.SetServerMetadata(entity.Id, metadata) c.Assert(err, gc.IsNil) } server, err := s.nova.GetServer(entity.Id) c.Assert(err, gc.IsNil) c.Assert(server.Metadata["k1"], gc.Equals, "v1.replacement") c.Assert(server.Metadata["k2"], gc.Equals, "v2.replacement") } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/nova/local_test.go000066400000000000000000000232421414605405700235420ustar00rootroot00000000000000package nova_test import ( "bytes" "log" "net/http" "net/http/httptest" "strings" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/client" "gopkg.in/goose.v1/errors" goosehttp "gopkg.in/goose.v1/http" "gopkg.in/goose.v1/identity" "gopkg.in/goose.v1/nova" "gopkg.in/goose.v1/testservices" "gopkg.in/goose.v1/testservices/hook" "gopkg.in/goose.v1/testservices/identityservice" "gopkg.in/goose.v1/testservices/openstackservice" ) func registerLocalTests() { // Test using numeric ids. gc.Suite(&localLiveSuite{ useNumericIds: true, }) // Test using string ids. gc.Suite(&localLiveSuite{ useNumericIds: false, }) } // localLiveSuite runs tests from LiveTests using a fake // nova server that runs within the test process itself. type localLiveSuite struct { LiveTests useNumericIds bool // The following attributes are for using testing doubles. Server *httptest.Server Mux *http.ServeMux oldHandler http.Handler openstack *openstackservice.Openstack retryErrorCount int // The current retry error count. retryErrorCountToSend int // The number of retry errors to send. noMoreIPs bool // If true, addFloatingIP will return ErrNoMoreFloatingIPs ipLimitExceeded bool // If true, addFloatingIP will return ErrIPLimitExceeded badTokens int // If > 0, authHook will set an invalid token in the AccessResponse data. } func (s *localLiveSuite) SetUpSuite(c *gc.C) { var idInfo string if s.useNumericIds { idInfo = "with numeric ids" } else { idInfo = "with string ids" } c.Logf("Using identity and nova service test doubles %s", idInfo) nova.UseNumericIds(s.useNumericIds) // Set up an Openstack service. s.cred = &identity.Credentials{ User: "fred", Secrets: "secret", Region: "some region", TenantName: "tenant", } var logMsg []string s.openstack, logMsg = openstackservice.New(s.cred, identity.AuthUserPass, false) for _, msg := range logMsg { c.Logf(msg) } s.openstack.SetupHTTP(nil) s.testFlavor = "m1.small" s.testImageId = "1" s.LiveTests.SetUpSuite(c) } func (s *localLiveSuite) TearDownSuite(c *gc.C) { s.LiveTests.TearDownSuite(c) s.openstack.Stop() } func (s *localLiveSuite) SetUpTest(c *gc.C) { s.retryErrorCount = 0 s.LiveTests.SetUpTest(c) } func (s *localLiveSuite) TearDownTest(c *gc.C) { s.LiveTests.TearDownTest(c) } // Additional tests to be run against the service double only go here. func (s *localLiveSuite) retryLimitHook(sc hook.ServiceControl) hook.ControlProcessor { return func(sc hook.ServiceControl, args ...interface{}) error { sendError := s.retryErrorCount < s.retryErrorCountToSend if sendError { s.retryErrorCount++ return testservices.RateLimitExceededError } return nil } } func (s *localLiveSuite) setupClient(c *gc.C, logger *log.Logger) *nova.Client { client := client.NewClient(s.cred, identity.AuthUserPass, logger) return nova.New(client) } func (s *localLiveSuite) setupRetryErrorTest(c *gc.C, logger *log.Logger) (*nova.Client, *nova.SecurityGroup) { novaClient := s.setupClient(c, logger) // Delete the artifact if it already exists. testGroup, err := novaClient.SecurityGroupByName("test_group") if err != nil { c.Assert(errors.IsNotFound(err), gc.Equals, true) } else { novaClient.DeleteSecurityGroup(testGroup.Id) c.Assert(err, gc.IsNil) } testGroup, err = novaClient.CreateSecurityGroup("test_group", "test") c.Assert(err, gc.IsNil) return novaClient, testGroup } // TestRateLimitRetry checks that when we make too many requests and receive a Retry-After response, the retry // occurs and the request ultimately succeeds. func (s *localLiveSuite) TestRateLimitRetry(c *gc.C) { // Capture the logged output so we can check for retry messages. var logout bytes.Buffer logger := log.New(&logout, "", log.LstdFlags) novaClient, testGroup := s.setupRetryErrorTest(c, logger) s.retryErrorCountToSend = goosehttp.MaxSendAttempts - 1 s.openstack.Nova.RegisterControlPoint("removeSecurityGroup", s.retryLimitHook(s.openstack.Nova)) defer s.openstack.Nova.RegisterControlPoint("removeSecurityGroup", nil) err := novaClient.DeleteSecurityGroup(testGroup.Id) c.Assert(err, gc.IsNil) // Ensure we got at least one retry message. output := logout.String() c.Assert(strings.Contains(output, "Too many requests, retrying in"), gc.Equals, true) } // TestRateLimitRetryExceeded checks that an error is raised if too many retry responses are received from the server. func (s *localLiveSuite) TestRateLimitRetryExceeded(c *gc.C) { novaClient, testGroup := s.setupRetryErrorTest(c, nil) s.retryErrorCountToSend = goosehttp.MaxSendAttempts s.openstack.Nova.RegisterControlPoint("removeSecurityGroup", s.retryLimitHook(s.openstack.Nova)) defer s.openstack.Nova.RegisterControlPoint("removeSecurityGroup", nil) err := novaClient.DeleteSecurityGroup(testGroup.Id) c.Assert(err, gc.Not(gc.IsNil)) c.Assert(err.Error(), gc.Matches, "(.|\n)*Maximum number of attempts.*") } func (s *localLiveSuite) addFloatingIPHook(sc hook.ServiceControl) hook.ControlProcessor { return func(sc hook.ServiceControl, args ...interface{}) error { if s.noMoreIPs { return testservices.NoMoreFloatingIPs } else if s.ipLimitExceeded { return testservices.IPLimitExceeded } return nil } } func (s *localLiveSuite) TestAddFloatingIPErrors(c *gc.C) { novaClient := s.setupClient(c, nil) fips, err := novaClient.ListFloatingIPs() c.Assert(err, gc.IsNil) c.Assert(fips, gc.HasLen, 0) cleanup := s.openstack.Nova.RegisterControlPoint("addFloatingIP", s.addFloatingIPHook(s.openstack.Nova)) defer cleanup() s.noMoreIPs = true fip, err := novaClient.AllocateFloatingIP() c.Assert(err, gc.ErrorMatches, "(.|\n)*Zero floating ips available.*") c.Assert(fip, gc.IsNil) s.noMoreIPs = false s.ipLimitExceeded = true fip, err = novaClient.AllocateFloatingIP() c.Assert(err, gc.ErrorMatches, "(.|\n)*Maximum number of floating ips exceeded.*") c.Assert(fip, gc.IsNil) s.ipLimitExceeded = false fip, err = novaClient.AllocateFloatingIP() c.Assert(err, gc.IsNil) c.Assert(fip.IP, gc.Not(gc.Equals), "") } func (s *localLiveSuite) authHook(sc hook.ServiceControl) hook.ControlProcessor { return func(sc hook.ServiceControl, args ...interface{}) error { res := args[0].(*identityservice.AccessResponse) if s.badTokens > 0 { res.Access.Token.Id = "xxx" s.badTokens-- } return nil } } func (s *localLiveSuite) TestReauthenticate(c *gc.C) { novaClient := s.setupClient(c, nil) up := s.openstack.Identity.(*identityservice.UserPass) cleanup := up.RegisterControlPoint("authorisation", s.authHook(up)) defer cleanup() // An invalid token is returned after the first authentication step, resulting in the ListServers call // returning a 401. Subsequent authentication calls return the correct token so the auth retry does it's job. s.badTokens = 1 _, err := novaClient.ListServers(nil) c.Assert(err, gc.IsNil) } func (s *localLiveSuite) TestReauthenticateFailure(c *gc.C) { novaClient := s.setupClient(c, nil) up := s.openstack.Identity.(*identityservice.UserPass) cleanup := up.RegisterControlPoint("authorisation", s.authHook(up)) defer cleanup() // If the re-authentication fails, ensure an Unauthorised error is returned. s.badTokens = 2 _, err := novaClient.ListServers(nil) c.Assert(errors.IsUnauthorised(err), gc.Equals, true) } func (s *localLiveSuite) TestListAvailabilityZonesUnimplemented(c *gc.C) { // When the test service has no availability zones registered, // the /os-availability-zone API will return 404. We swallow // that error. s.openstack.Nova.SetAvailabilityZones() listedZones, err := s.nova.ListAvailabilityZones() c.Assert(err, gc.ErrorMatches, "the server does not support availability zones(.|\n)*") c.Assert(listedZones, gc.HasLen, 0) } func (s *localLiveSuite) setAvailabilityZones() []nova.AvailabilityZone { zones := []nova.AvailabilityZone{ {Name: "az1"}, { Name: "az2", State: nova.AvailabilityZoneState{Available: true}, }, } s.openstack.Nova.SetAvailabilityZones(zones...) return zones } func (s *localLiveSuite) TestListAvailabilityZones(c *gc.C) { zones := s.setAvailabilityZones() listedZones, err := s.nova.ListAvailabilityZones() c.Assert(err, gc.IsNil) c.Assert(listedZones, gc.DeepEquals, zones) } func (s *localLiveSuite) TestRunServerAvailabilityZone(c *gc.C) { s.setAvailabilityZones() inst, err := s.runServerAvailabilityZone("az2") c.Assert(err, gc.IsNil) defer s.nova.DeleteServer(inst.Id) server, err := s.nova.GetServer(inst.Id) c.Assert(err, gc.IsNil) c.Assert(server.AvailabilityZone, gc.Equals, "az2") } func (s *localLiveSuite) TestRunServerAvailabilityZoneNotAvailable(c *gc.C) { s.setAvailabilityZones() // az1 is known, but not currently available. _, err := s.runServerAvailabilityZone("az1") c.Assert(err, gc.ErrorMatches, "(.|\n)*The requested availability zone is not available(.|\n)*") } func (s *localLiveSuite) TestVolumeAttachments(c *gc.C) { instance, err := s.createInstance("test-instance") c.Assert(err, gc.IsNil) // Test attaching a volume. volAttachment, err := s.nova.AttachVolume(instance.Id, "volume-id", "/dev/sda1") c.Assert(err, gc.IsNil) c.Check(volAttachment.ServerId, gc.Equals, instance.Id) c.Check(volAttachment.VolumeId, gc.Equals, "volume-id") // Test listing volumes. volAttachments, err := s.nova.ListVolumeAttachments(instance.Id) c.Assert(err, gc.IsNil) c.Assert(volAttachments, gc.HasLen, 1) c.Check(volAttachments[0].ServerId, gc.Equals, instance.Id) c.Check(volAttachments[0].VolumeId, gc.Equals, "volume-id") // Test detaching volumes. err = s.nova.DetachVolume(instance.Id, volAttachment.Id) c.Assert(err, gc.IsNil) volAttachments, err = s.nova.ListVolumeAttachments(instance.Id) c.Assert(err, gc.IsNil) c.Assert(volAttachments, gc.HasLen, 0) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/nova/networks.go000066400000000000000000000025571414605405700232730ustar00rootroot00000000000000// Copyright 2014 Canonical Ltd. // Licensed under the LGPLv3, see COPYING and COPYING.LESSER file for details. // Nova api calls for managing networks, which may use either the old // nova-network code or delegate through to the neutron api. // See documentation at: // package nova import ( "gopkg.in/goose.v1/client" "gopkg.in/goose.v1/errors" goosehttp "gopkg.in/goose.v1/http" ) const ( apiNetworks = "os-networks" // The os-tenant-networks extension is a newer addition aimed at exposing // management of networks to unprivileged accounts. Not used at present. apiTenantNetworks = "os-tenant-networks" ) // Network contains details about a labeled network type Network struct { Id string `json:"id"` // UUID of the resource Label string `json:"label"` // User-provided name for the network range Cidr string `json:"cidr"` // IP range covered by the network } // ListNetworks gives details on available networks func (c *Client) ListNetworks() ([]Network, error) { var resp struct { Networks []Network `json:"networks"` } requestData := goosehttp.RequestData{RespValue: &resp} err := c.client.SendRequest(client.GET, "compute", "v2", apiNetworks, &requestData) if err != nil { return nil, errors.Newf(err, "failed to get list of networks") } return resp.Networks, nil } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/nova/nova.go000066400000000000000000000744661414605405700223720ustar00rootroot00000000000000// goose/nova - Go package to interact with OpenStack Compute (Nova) API. // See http://docs.openstack.org/api/openstack-compute/2/content/. package nova import ( "fmt" "net/http" "net/url" "reflect" "gopkg.in/goose.v1/client" "gopkg.in/goose.v1/errors" goosehttp "gopkg.in/goose.v1/http" ) // API URL parts. const ( apiFlavors = "flavors" apiFlavorsDetail = "flavors/detail" apiServers = "servers" apiServersDetail = "servers/detail" apiSecurityGroups = "os-security-groups" apiSecurityGroupRules = "os-security-group-rules" apiFloatingIPs = "os-floating-ips" apiAvailabilityZone = "os-availability-zone" apiVolumeAttachments = "os-volume_attachments" ) // Server status values. const ( StatusActive = "ACTIVE" // The server is active. StatusBuild = "BUILD" // The server has not finished the original build process. StatusBuildSpawning = "BUILD(spawning)" // The server has not finished the original build process but networking works (HP Cloud specific) StatusDeleted = "DELETED" // The server is deleted. StatusError = "ERROR" // The server is in error. StatusHardReboot = "HARD_REBOOT" // The server is hard rebooting. StatusPassword = "PASSWORD" // The password is being reset on the server. StatusReboot = "REBOOT" // The server is in a soft reboot state. StatusRebuild = "REBUILD" // The server is currently being rebuilt from an image. StatusRescue = "RESCUE" // The server is in rescue mode. StatusResize = "RESIZE" // Server is performing the differential copy of data that changed during its initial copy. StatusShutoff = "SHUTOFF" // The virtual machine (VM) was powered down by the user, but not through the OpenStack Compute API. StatusSuspended = "SUSPENDED" // The server is suspended, either by request or necessity. StatusUnknown = "UNKNOWN" // The state of the server is unknown. Contact your cloud provider. StatusVerifyResize = "VERIFY_RESIZE" // System is awaiting confirmation that the server is operational after a move or resize. ) // Filter keys. const ( FilterStatus = "status" // The server status. See Server Status Values. FilterImage = "image" // The image reference specified as an ID or full URL. FilterFlavor = "flavor" // The flavor reference specified as an ID or full URL. FilterServer = "name" // The server name. FilterMarker = "marker" // The ID of the last item in the previous list. FilterLimit = "limit" // The page size. FilterChangesSince = "changes-since" // The changes-since time. The list contains servers that have been deleted since the changes-since time. ) // Client provides a means to access the OpenStack Compute Service. type Client struct { client client.Client } // New creates a new Client. func New(client client.Client) *Client { return &Client{client} } // ---------------------------------------------------------------------------- // Filtering helper. // // Filter builds filtering parameters to be used in an OpenStack query which supports // filtering. For example: // // filter := NewFilter() // filter.Set(nova.FilterServer, "server_name") // filter.Set(nova.FilterStatus, nova.StatusBuild) // resp, err := nova.ListServers(filter) // type Filter struct { v url.Values } // NewFilter creates a new Filter. func NewFilter() *Filter { return &Filter{make(url.Values)} } func (f *Filter) Set(filter, value string) { f.v.Set(filter, value) } // Link describes a link to a flavor or server. type Link struct { Href string Rel string Type string } // Entity describe a basic information about a flavor or server. type Entity struct { Id string `json:"-"` UUID string `json:"uuid"` Links []Link `json:"links"` Name string `json:"name"` } func stringValue(item interface{}, attr string) string { return reflect.ValueOf(item).FieldByName(attr).String() } // Allow Entity slices to be sorted by named attribute. type EntitySortBy struct { Attr string Entities []Entity } func (e EntitySortBy) Len() int { return len(e.Entities) } func (e EntitySortBy) Less(i, j int) bool { return stringValue(e.Entities[i], e.Attr) < stringValue(e.Entities[j], e.Attr) } func (e EntitySortBy) Swap(i, j int) { e.Entities[i], e.Entities[j] = e.Entities[j], e.Entities[i] } // ListFlavours lists IDs, names, and links for available flavors. func (c *Client) ListFlavors() ([]Entity, error) { var resp struct { Flavors []Entity } requestData := goosehttp.RequestData{RespValue: &resp} err := c.client.SendRequest(client.GET, "compute", "v2", apiFlavors, &requestData) if err != nil { return nil, errors.Newf(err, "failed to get list of flavours") } return resp.Flavors, nil } // FlavorDetail describes detailed information about a flavor. type FlavorDetail struct { Name string RAM int // Available RAM, in MB VCPUs int // Number of virtual CPU (cores) Disk int // Available root partition space, in GB Id string `json:"-"` Links []Link } // Allow FlavorDetail slices to be sorted by named attribute. type FlavorDetailSortBy struct { Attr string FlavorDetails []FlavorDetail } func (e FlavorDetailSortBy) Len() int { return len(e.FlavorDetails) } func (e FlavorDetailSortBy) Less(i, j int) bool { return stringValue(e.FlavorDetails[i], e.Attr) < stringValue(e.FlavorDetails[j], e.Attr) } func (e FlavorDetailSortBy) Swap(i, j int) { e.FlavorDetails[i], e.FlavorDetails[j] = e.FlavorDetails[j], e.FlavorDetails[i] } // ListFlavorsDetail lists all details for available flavors. func (c *Client) ListFlavorsDetail() ([]FlavorDetail, error) { var resp struct { Flavors []FlavorDetail } requestData := goosehttp.RequestData{RespValue: &resp} err := c.client.SendRequest(client.GET, "compute", "v2", apiFlavorsDetail, &requestData) if err != nil { return nil, errors.Newf(err, "failed to get list of flavour details") } return resp.Flavors, nil } // ListServers lists IDs, names, and links for all servers. func (c *Client) ListServers(filter *Filter) ([]Entity, error) { var resp struct { Servers []Entity } var params *url.Values if filter != nil { params = &filter.v } requestData := goosehttp.RequestData{RespValue: &resp, Params: params, ExpectedStatus: []int{http.StatusOK}} err := c.client.SendRequest(client.GET, "compute", "v2", apiServers, &requestData) if err != nil { return nil, errors.Newf(err, "failed to get list of servers") } return resp.Servers, nil } // IPAddress describes a single IPv4/6 address of a server. type IPAddress struct { Version int `json:"version"` Address string `json:"addr"` Type string `json:"OS-EXT-IPS:type"` // fixed or floating } // ServerDetail describes a server in more detail. // See: http://docs.openstack.org/api/openstack-compute/2/content/Extensions-d1e1444.html#ServersCBSJ type ServerDetail struct { // AddressIPv4 and AddressIPv6 hold the first public IPv4 or IPv6 // address of the server, or "" if no floating IP is assigned. AddressIPv4 string AddressIPv6 string // Addresses holds the list of all IP addresses assigned to this // server, grouped by "network" name ("public", "private" or a // custom name). Addresses map[string][]IPAddress // Created holds the creation timestamp of the server // in RFC3339 format. Created string Flavor Entity HostId string Id string `json:"-"` UUID string Image Entity Links []Link Name string Metadata map[string]string // HP Cloud returns security groups in server details. Groups []Entity `json:"security_groups"` // Progress holds the completion percentage of // the current operation Progress int // Status holds the current status of the server, // one of the Status* constants. Status string TenantId string `json:"tenant_id"` // Updated holds the timestamp of the last update // to the server in RFC3339 format. Updated string UserId string `json:"user_id"` AvailabilityZone string `json:"OS-EXT-AZ:availability_zone"` } // ListServersDetail lists all details for available servers. func (c *Client) ListServersDetail(filter *Filter) ([]ServerDetail, error) { var resp struct { Servers []ServerDetail } var params *url.Values if filter != nil { params = &filter.v } requestData := goosehttp.RequestData{RespValue: &resp, Params: params} err := c.client.SendRequest(client.GET, "compute", "v2", apiServersDetail, &requestData) if err != nil { return nil, errors.Newf(err, "failed to get list of server details") } return resp.Servers, nil } // GetServer lists details for the specified server. func (c *Client) GetServer(serverId string) (*ServerDetail, error) { var resp struct { Server ServerDetail } url := fmt.Sprintf("%s/%s", apiServers, serverId) requestData := goosehttp.RequestData{RespValue: &resp} err := c.client.SendRequest(client.GET, "compute", "v2", url, &requestData) if err != nil { return nil, errors.Newf(err, "failed to get details for serverId: %s", serverId) } return &resp.Server, nil } // DeleteServer terminates the specified server. func (c *Client) DeleteServer(serverId string) error { var resp struct { Server ServerDetail } url := fmt.Sprintf("%s/%s", apiServers, serverId) requestData := goosehttp.RequestData{RespValue: &resp, ExpectedStatus: []int{http.StatusNoContent}} err := c.client.SendRequest(client.DELETE, "compute", "v2", url, &requestData) if err != nil { err = errors.Newf(err, "failed to delete server with serverId: %s", serverId) } return err } type SecurityGroupName struct { Name string `json:"name"` } // ServerNetworks sets what networks a server should be connected to on boot. // - FixedIp may be supplied only when NetworkId is also given. // - PortId may be supplied only if neither NetworkId or FixedIp is set. type ServerNetworks struct { NetworkId string `json:"uuid,omitempty"` FixedIp string `json:"fixed_ip,omitempty"` PortId string `json:"port,omitempty"` } // RunServerOpts defines required and optional arguments for RunServer(). type RunServerOpts struct { Name string `json:"name"` // Required FlavorId string `json:"flavorRef"` // Required ImageId string `json:"imageRef"` // Required UserData []byte `json:"user_data"` // Optional SecurityGroupNames []SecurityGroupName `json:"security_groups"` // Optional Networks []ServerNetworks `json:"networks"` // Optional AvailabilityZone string `json:"availability_zone,omitempty"` // Optional Metadata map[string]string `json:"metadata,omitempty"` // Optional ConfigDrive bool `json:"config_drive,omitempty"` // Optional } // RunServer creates a new server, based on the given RunServerOpts. func (c *Client) RunServer(opts RunServerOpts) (*Entity, error) { var req struct { Server RunServerOpts `json:"server"` } req.Server = opts // opts.UserData gets serialized to base64-encoded string automatically var resp struct { Server Entity `json:"server"` } requestData := goosehttp.RequestData{ReqValue: req, RespValue: &resp, ExpectedStatus: []int{http.StatusAccepted}} err := c.client.SendRequest(client.POST, "compute", "v2", apiServers, &requestData) if err != nil { return nil, errors.Newf(err, "failed to run a server with %#v", opts) } return &resp.Server, nil } type serverUpdateNameOpts struct { Name string `json:"name"` } // UpdateServerName updates the name of the given server. func (c *Client) UpdateServerName(serverID, name string) (*Entity, error) { var req struct { Server serverUpdateNameOpts `json:"server"` } var resp struct { Server Entity `json:"server"` } req.Server = serverUpdateNameOpts{Name: name} requestData := goosehttp.RequestData{ReqValue: req, RespValue: &resp, ExpectedStatus: []int{http.StatusOK}} url := fmt.Sprintf("%s/%s", apiServers, serverID) err := c.client.SendRequest(client.PUT, "compute", "v2", url, &requestData) if err != nil { return nil, errors.Newf(err, "failed to update server name to %q", name) } return &resp.Server, nil } // SecurityGroupRef refers to an existing named security group type SecurityGroupRef struct { TenantId string `json:"tenant_id"` Name string `json:"name"` } // SecurityGroupRule describes a rule of a security group. There are 2 // basic rule types: ingress and group rules (see RuleInfo struct). type SecurityGroupRule struct { FromPort *int `json:"from_port"` // Can be nil IPProtocol *string `json:"ip_protocol"` // Can be nil ToPort *int `json:"to_port"` // Can be nil ParentGroupId string `json:"-"` IPRange map[string]string `json:"ip_range"` // Can be empty Id string `json:"-"` Group SecurityGroupRef } // SecurityGroup describes a single security group in OpenStack. type SecurityGroup struct { Rules []SecurityGroupRule TenantId string `json:"tenant_id"` Id string `json:"-"` Name string Description string } // ListSecurityGroups lists IDs, names, and other details for all security groups. func (c *Client) ListSecurityGroups() ([]SecurityGroup, error) { var resp struct { Groups []SecurityGroup `json:"security_groups"` } requestData := goosehttp.RequestData{RespValue: &resp} err := c.client.SendRequest(client.GET, "compute", "v2", apiSecurityGroups, &requestData) if err != nil { return nil, errors.Newf(err, "failed to list security groups") } return resp.Groups, nil } // GetSecurityGroupByName returns the named security group. // Note: due to lack of filtering support when querying security groups, this is not an efficient implementation // but it's all we can do for now. func (c *Client) SecurityGroupByName(name string) (*SecurityGroup, error) { // OpenStack does not support group filtering, so we need to load them all and manually search by name. groups, err := c.ListSecurityGroups() if err != nil { return nil, err } for _, group := range groups { if group.Name == name { return &group, nil } } return nil, errors.NewNotFoundf(nil, "", "Security group %s not found.", name) } // GetServerSecurityGroups list security groups for a specific server. func (c *Client) GetServerSecurityGroups(serverId string) ([]SecurityGroup, error) { var resp struct { Groups []SecurityGroup `json:"security_groups"` } url := fmt.Sprintf("%s/%s/%s", apiServers, serverId, apiSecurityGroups) requestData := goosehttp.RequestData{RespValue: &resp} err := c.client.SendRequest(client.GET, "compute", "v2", url, &requestData) if err != nil { // Sadly HP Cloud lacks the necessary API and also doesn't provide full SecurityGroup lookup. // The best we can do for now is to use just the Id and Name from the group entities. if errors.IsNotFound(err) { serverDetails, err := c.GetServer(serverId) if err == nil { result := make([]SecurityGroup, len(serverDetails.Groups)) for i, e := range serverDetails.Groups { result[i] = SecurityGroup{ Id: e.Id, Name: e.Name, } } return result, nil } } return nil, errors.Newf(err, "failed to list server (%s) security groups", serverId) } return resp.Groups, nil } // CreateSecurityGroup creates a new security group. func (c *Client) CreateSecurityGroup(name, description string) (*SecurityGroup, error) { var req struct { SecurityGroup struct { Name string `json:"name"` Description string `json:"description"` } `json:"security_group"` } req.SecurityGroup.Name = name req.SecurityGroup.Description = description var resp struct { SecurityGroup SecurityGroup `json:"security_group"` } requestData := goosehttp.RequestData{ReqValue: req, RespValue: &resp, ExpectedStatus: []int{http.StatusOK}} err := c.client.SendRequest(client.POST, "compute", "v2", apiSecurityGroups, &requestData) if err != nil { return nil, errors.Newf(err, "failed to create a security group with name: %s", name) } return &resp.SecurityGroup, nil } // DeleteSecurityGroup deletes the specified security group. func (c *Client) DeleteSecurityGroup(groupId string) error { url := fmt.Sprintf("%s/%s", apiSecurityGroups, groupId) requestData := goosehttp.RequestData{ExpectedStatus: []int{http.StatusAccepted}} err := c.client.SendRequest(client.DELETE, "compute", "v2", url, &requestData) if err != nil { err = errors.Newf(err, "failed to delete security group with id: %s", groupId) } return err } // UpdateSecurityGroup updates the name and description of the given group. func (c *Client) UpdateSecurityGroup(groupId, name, description string) (*SecurityGroup, error) { var req struct { SecurityGroup struct { Name string `json:"name"` Description string `json:"description"` } `json:"security_group"` } req.SecurityGroup.Name = name req.SecurityGroup.Description = description var resp struct { SecurityGroup SecurityGroup `json:"security_group"` } url := fmt.Sprintf("%s/%s", apiSecurityGroups, groupId) requestData := goosehttp.RequestData{ReqValue: req, RespValue: &resp, ExpectedStatus: []int{http.StatusOK}} err := c.client.SendRequest(client.PUT, "compute", "v2", url, &requestData) if err != nil { return nil, errors.Newf(err, "failed to update security group with Id %s to name: %s", groupId, name) } return &resp.SecurityGroup, nil } // RuleInfo allows the callers of CreateSecurityGroupRule() to // create 2 types of security group rules: ingress rules and group // rules. The difference stems from how the "source" is defined. // It can be either: // 1. Ingress rules - specified directly with any valid subnet mask // in CIDR format (e.g. "192.168.0.0/16"); // 2. Group rules - specified indirectly by giving a source group, // which can be any user's group (different tenant ID). // // Every rule works as an iptables ACCEPT rule, thus a group/ with no // rules does not allow ingress at all. Rules can be added and removed // while the server(s) are running. The set of security groups that // apply to a server is changed only when the server is // started. Adding or removing a security group on a running server // will not take effect until that server is restarted. However, // changing rules of existing groups will take effect immediately. // // For more information: // http://docs.openstack.org/developer/nova/nova.concepts.html#concept-security-groups // Nova source: https://github.com/openstack/nova.git type RuleInfo struct { /// IPProtocol is optional, and if specified must be "tcp", "udp" or // "icmp" (in this case, both FromPort and ToPort can be -1). IPProtocol string `json:"ip_protocol"` // FromPort and ToPort are both optional, and if specifed must be // integers between 1 and 65535 (valid TCP port numbers). -1 is a // special value, meaning "use default" (e.g. for ICMP). FromPort int `json:"from_port"` ToPort int `json:"to_port"` // Cidr cannot be specified with GroupId. Ingress rules need a valid // subnet mast in CIDR format here, while if GroupID is specifed, it // means you're adding a group rule, specifying source group ID, which // must exist already and can be equal to ParentGroupId). // need Cidr, while Cidr string `json:"cidr"` GroupId *string `json:"-"` // ParentGroupId is always required and specifies the group to which // the rule is added. ParentGroupId string `json:"-"` } // CreateSecurityGroupRule creates a security group rule. // It can either be an ingress rule or group rule (see the // description of RuleInfo). func (c *Client) CreateSecurityGroupRule(ruleInfo RuleInfo) (*SecurityGroupRule, error) { var req struct { SecurityGroupRule RuleInfo `json:"security_group_rule"` } req.SecurityGroupRule = ruleInfo var resp struct { SecurityGroupRule SecurityGroupRule `json:"security_group_rule"` } requestData := goosehttp.RequestData{ReqValue: req, RespValue: &resp} err := c.client.SendRequest(client.POST, "compute", "v2", apiSecurityGroupRules, &requestData) if err != nil { return nil, errors.Newf(err, "failed to create a rule for the security group with id: %v", ruleInfo.GroupId) } return &resp.SecurityGroupRule, nil } // DeleteSecurityGroupRule deletes the specified security group rule. func (c *Client) DeleteSecurityGroupRule(ruleId string) error { url := fmt.Sprintf("%s/%s", apiSecurityGroupRules, ruleId) requestData := goosehttp.RequestData{ExpectedStatus: []int{http.StatusAccepted}} err := c.client.SendRequest(client.DELETE, "compute", "v2", url, &requestData) if err != nil { err = errors.Newf(err, "failed to delete security group rule with id: %s", ruleId) } return err } // AddServerSecurityGroup adds a security group to the specified server. func (c *Client) AddServerSecurityGroup(serverId, groupName string) error { var req struct { AddSecurityGroup struct { Name string `json:"name"` } `json:"addSecurityGroup"` } req.AddSecurityGroup.Name = groupName url := fmt.Sprintf("%s/%s/action", apiServers, serverId) requestData := goosehttp.RequestData{ReqValue: req, ExpectedStatus: []int{http.StatusAccepted}} err := c.client.SendRequest(client.POST, "compute", "v2", url, &requestData) if err != nil { err = errors.Newf(err, "failed to add security group '%s' to server with id: %s", groupName, serverId) } return err } // RemoveServerSecurityGroup removes a security group from the specified server. func (c *Client) RemoveServerSecurityGroup(serverId, groupName string) error { var req struct { RemoveSecurityGroup struct { Name string `json:"name"` } `json:"removeSecurityGroup"` } req.RemoveSecurityGroup.Name = groupName url := fmt.Sprintf("%s/%s/action", apiServers, serverId) requestData := goosehttp.RequestData{ReqValue: req, ExpectedStatus: []int{http.StatusAccepted}} err := c.client.SendRequest(client.POST, "compute", "v2", url, &requestData) if err != nil { err = errors.Newf(err, "failed to remove security group '%s' from server with id: %s", groupName, serverId) } return err } // FloatingIP describes a floating (public) IP address, which can be // assigned to a server, thus allowing connections from outside. type FloatingIP struct { // FixedIP holds the private IP address of the machine (when assigned) FixedIP *string `json:"fixed_ip"` Id string `json:"-"` // InstanceId holds the instance id of the machine, if this FIP is assigned to one InstanceId *string `json:"-"` IP string `json:"ip"` Pool string `json:"pool"` } // ListFloatingIPs lists floating IP addresses associated with the tenant or account. func (c *Client) ListFloatingIPs() ([]FloatingIP, error) { var resp struct { FloatingIPs []FloatingIP `json:"floating_ips"` } requestData := goosehttp.RequestData{RespValue: &resp} err := c.client.SendRequest(client.GET, "compute", "v2", apiFloatingIPs, &requestData) if err != nil { return nil, errors.Newf(err, "failed to list floating ips") } return resp.FloatingIPs, nil } // GetFloatingIP lists details of the floating IP address associated with specified id. func (c *Client) GetFloatingIP(ipId string) (*FloatingIP, error) { var resp struct { FloatingIP FloatingIP `json:"floating_ip"` } url := fmt.Sprintf("%s/%s", apiFloatingIPs, ipId) requestData := goosehttp.RequestData{RespValue: &resp} err := c.client.SendRequest(client.GET, "compute", "v2", url, &requestData) if err != nil { return nil, errors.Newf(err, "failed to get floating ip %s details", ipId) } return &resp.FloatingIP, nil } // AllocateFloatingIP allocates a new floating IP address to a tenant or account. func (c *Client) AllocateFloatingIP() (*FloatingIP, error) { var resp struct { FloatingIP FloatingIP `json:"floating_ip"` } requestData := goosehttp.RequestData{RespValue: &resp} err := c.client.SendRequest(client.POST, "compute", "v2", apiFloatingIPs, &requestData) if err != nil { return nil, errors.Newf(err, "failed to allocate a floating ip") } return &resp.FloatingIP, nil } // DeleteFloatingIP deallocates the floating IP address associated with the specified id. func (c *Client) DeleteFloatingIP(ipId string) error { url := fmt.Sprintf("%s/%s", apiFloatingIPs, ipId) requestData := goosehttp.RequestData{ExpectedStatus: []int{http.StatusAccepted}} err := c.client.SendRequest(client.DELETE, "compute", "v2", url, &requestData) if err != nil { err = errors.Newf(err, "failed to delete floating ip %s details", ipId) } return err } // AddServerFloatingIP assigns a floating IP address to the specified server. func (c *Client) AddServerFloatingIP(serverId, address string) error { var req struct { AddFloatingIP struct { Address string `json:"address"` } `json:"addFloatingIp"` } req.AddFloatingIP.Address = address url := fmt.Sprintf("%s/%s/action", apiServers, serverId) requestData := goosehttp.RequestData{ReqValue: req, ExpectedStatus: []int{http.StatusAccepted}} err := c.client.SendRequest(client.POST, "compute", "v2", url, &requestData) if err != nil { err = errors.Newf(err, "failed to add floating ip %s to server with id: %s", address, serverId) } return err } // RemoveServerFloatingIP removes a floating IP address from the specified server. func (c *Client) RemoveServerFloatingIP(serverId, address string) error { var req struct { RemoveFloatingIP struct { Address string `json:"address"` } `json:"removeFloatingIp"` } req.RemoveFloatingIP.Address = address url := fmt.Sprintf("%s/%s/action", apiServers, serverId) requestData := goosehttp.RequestData{ReqValue: req, ExpectedStatus: []int{http.StatusAccepted}} err := c.client.SendRequest(client.POST, "compute", "v2", url, &requestData) if err != nil { err = errors.Newf(err, "failed to remove floating ip %s from server with id: %s", address, serverId) } return err } // AvailabilityZone identifies an availability zone, and describes its state. type AvailabilityZone struct { Name string `json:"zoneName"` State AvailabilityZoneState `json:"zoneState"` } // AvailabilityZoneState describes an availability zone's state. type AvailabilityZoneState struct { Available bool } // ListAvailabilityZones lists all availability zones. // // Availability zones are an OpenStack extension; if the server does not // support them, then an error satisfying errors.IsNotImplemented will be // returned. func (c *Client) ListAvailabilityZones() ([]AvailabilityZone, error) { var resp struct { AvailabilityZoneInfo []AvailabilityZone } requestData := goosehttp.RequestData{RespValue: &resp} err := c.client.SendRequest(client.GET, "compute", "v2", apiAvailabilityZone, &requestData) if errors.IsNotFound(err) { // Availability zones are an extension, so don't // return an error if the API does not exist. return nil, errors.NewNotImplementedf( err, nil, "the server does not support availability zones", ) } if err != nil { return nil, errors.Newf(err, "failed to get list of availability zones") } return resp.AvailabilityZoneInfo, nil } // VolumeAttachment represents both the request and response for // attaching volumes. type VolumeAttachment struct { Device *string `json:"device,omitempty"` Id string `json:"id,omitempty"` ServerId string `json:"serverId,omitempty"` VolumeId string `json:"volumeId"` } // AttachVolume attaches the given volumeId to the given serverId at // mount point specified in device. Note that the server must support // the os-volume_attachments attachment; if it does not, an error will // be returned stating such. func (c *Client) AttachVolume(serverId, volumeId, device string) (*VolumeAttachment, error) { type volumeAttachment struct { VolumeAttachment VolumeAttachment `json:"volumeAttachment"` } var devicePtr *string if device != "" { devicePtr = &device } var resp volumeAttachment requestData := goosehttp.RequestData{ ReqValue: &volumeAttachment{VolumeAttachment{ VolumeId: volumeId, Device: devicePtr, }}, RespValue: &resp, } url := fmt.Sprintf("%s/%s/%s", apiServers, serverId, apiVolumeAttachments) err := c.client.SendRequest(client.POST, "compute", "v2", url, &requestData) if errors.IsNotFound(err) { return nil, errors.NewNotImplementedf( err, nil, "the server does not support attaching volumes", ) } if err != nil { return nil, errors.Newf(err, "failed to attach volume") } return &resp.VolumeAttachment, nil } // DetachVolume detaches the volume with the given attachmentId from // the server with the given serverId. func (c *Client) DetachVolume(serverId, attachmentId string) error { requestData := goosehttp.RequestData{ ExpectedStatus: []int{http.StatusAccepted}, } url := fmt.Sprintf("%s/%s/%s/%s", apiServers, serverId, apiVolumeAttachments, attachmentId) err := c.client.SendRequest(client.DELETE, "compute", "v2", url, &requestData) if errors.IsNotFound(err) { return errors.NewNotImplementedf( err, nil, "the server does not support deleting attached volumes", ) } if err != nil { return errors.Newf(err, "failed to delete volume attachment") } return nil } // ListVolumeAttachments lists the volumes currently attached to the // server with the given serverId. func (c *Client) ListVolumeAttachments(serverId string) ([]VolumeAttachment, error) { var resp struct { VolumeAttachments []VolumeAttachment `json:"volumeAttachments"` } requestData := goosehttp.RequestData{ RespValue: &resp, } url := fmt.Sprintf("%s/%s/%s", apiServers, serverId, apiVolumeAttachments) err := c.client.SendRequest(client.GET, "compute", "v2", url, &requestData) if errors.IsNotFound(err) { return nil, errors.NewNotImplementedf( err, nil, "the server does not support listing attached volumes", ) } if err != nil { return nil, errors.Newf(err, "failed to list volume attachments") } return resp.VolumeAttachments, nil } // SetServerMetadata sets metadata on a server. Replaces metadata // items that match keys - doesn't modify items that aren't in the // request. // See https://developer.openstack.org/api-ref/compute/?expanded=update-metadata-items-detail#update-metadata-items func (c *Client) SetServerMetadata(serverId string, metadata map[string]string) error { req := struct { Metadata map[string]string `json:"metadata"` }{metadata} url := fmt.Sprintf("%s/%s/metadata", apiServers, serverId) requestData := goosehttp.RequestData{ ReqValue: req, ExpectedStatus: []int{http.StatusOK}, } err := c.client.SendRequest(client.POST, "compute", "v2", url, &requestData) if err != nil { err = errors.Newf(err, "failed to set metadata %v on server with id: %s", metadata, serverId) } return err } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/nova/nova_test.go000066400000000000000000000035401414605405700234120ustar00rootroot00000000000000package nova_test import ( "flag" "reflect" "testing" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/identity" ) // imageDetails specify parameters used to start a test machine for the live tests. type imageDetails struct { flavor string imageId string vendor string } // Out-of-the-box, we support live testing using Canonistack or HP Cloud. var testConstraints = map[string]imageDetails{ "canonistack": { flavor: "m1.tiny", imageId: "f2ca48ce-30d5-4f1f-9075-12e64510368d"}, "hpcloud": { flavor: "standard.xsmall", imageId: "81078"}, } var live = flag.Bool("live", false, "Include live OpenStack tests") var vendor = flag.String("vendor", "", "The Openstack vendor to test against") var imageId = flag.String("image", "", "The image id for which a test service is to be started") var flavor = flag.String("flavor", "", "The flavor of the test service") func Test(t *testing.T) { if *live { // We can either specify a vendor, or imageId and flavor separately. var testImageDetails imageDetails if *vendor != "" { var ok bool if testImageDetails, ok = testConstraints[*vendor]; !ok { keys := reflect.ValueOf(testConstraints).MapKeys() t.Fatalf("Unknown vendor %s. Must be one of %s", *vendor, keys) } testImageDetails.vendor = *vendor } else { if *imageId == "" { t.Fatalf("Must specify image id to use for test instance, "+ "eg %s for Canonistack", "-image c876e5fe-abb0-41f0-8f29-f0b47481f523") } if *flavor == "" { t.Fatalf("Must specify flavor to use for test instance, "+ "eg %s for Canonistack", "-flavor m1.tiny") } testImageDetails = imageDetails{*flavor, *imageId, ""} } cred, err := identity.CompleteCredentialsFromEnv() if err != nil { t.Fatalf("Error setting up test suite: %s", err.Error()) } registerOpenStackTests(cred, testImageDetails) } registerLocalTests() gc.TestingT(t) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/swift/000077500000000000000000000000001414605405700212505ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/swift/live_test.go000066400000000000000000000164101414605405700235770ustar00rootroot00000000000000package swift_test import ( "bytes" "crypto/rand" "fmt" "io" "io/ioutil" "net/http" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/client" "gopkg.in/goose.v1/errors" "gopkg.in/goose.v1/identity" "gopkg.in/goose.v1/swift" ) func registerOpenStackTests(cred *identity.Credentials) { gc.Suite(&LiveTests{ cred: cred, }) gc.Suite(&LiveTestsPublicContainer{ cred: cred, }) } func randomName() string { buf := make([]byte, 8) _, err := io.ReadFull(rand.Reader, buf) if err != nil { panic(fmt.Sprintf("error from crypto rand: %v", err)) } return fmt.Sprintf("%x", buf) } type LiveTests struct { cred *identity.Credentials client client.AuthenticatingClient swift *swift.Client containerName string } func (s *LiveTests) SetUpSuite(c *gc.C) { s.containerName = "test_container" + randomName() s.client = client.NewClient(s.cred, identity.AuthUserPass, nil) s.swift = swift.New(s.client) } func (s *LiveTests) TearDownSuite(c *gc.C) { // noop, called by local test suite. } func (s *LiveTests) SetUpTest(c *gc.C) { assertCreateContainer(c, s.containerName, s.swift, swift.Private) } func (s *LiveTests) TearDownTest(c *gc.C) { err := s.swift.DeleteContainer(s.containerName) c.Check(err, gc.IsNil) } func assertCreateContainer(c *gc.C, container string, s *swift.Client, acl swift.ACL) { // The test container may exist already, so try and delete it. // If the result is a NotFound error, we don't care. err := s.DeleteContainer(container) if err != nil { c.Check(errors.IsNotFound(err), gc.Equals, true) } err = s.CreateContainer(container, acl) c.Assert(err, gc.IsNil) } func (s *LiveTests) TestObject(c *gc.C) { object := "test_obj1" data := "...some data..." err := s.swift.PutObject(s.containerName, object, []byte(data)) c.Check(err, gc.IsNil) objdata, err := s.swift.GetObject(s.containerName, object) c.Check(err, gc.IsNil) c.Check(string(objdata), gc.Equals, data) err = s.swift.DeleteObject(s.containerName, object) c.Assert(err, gc.IsNil) } func (s *LiveTests) TestObjectReader(c *gc.C) { object := "test_obj2" data := "...some data..." err := s.swift.PutReader(s.containerName, object, bytes.NewReader([]byte(data)), int64(len(data))) c.Check(err, gc.IsNil) r, headers, err := s.swift.GetReader(s.containerName, object) c.Check(err, gc.IsNil) readData, err := ioutil.ReadAll(r) c.Check(err, gc.IsNil) r.Close() c.Check(string(readData), gc.Equals, data) err = s.swift.DeleteObject(s.containerName, object) c.Assert(err, gc.IsNil) c.Check(headers.Get("Date"), gc.Not(gc.Equals), "") } func (s *LiveTests) TestList(c *gc.C) { data := "...some data..." var files []string = make([]string, 2) var fileNames map[string]bool = make(map[string]bool) for i := 0; i < 2; i++ { files[i] = fmt.Sprintf("test_obj%d", i) fileNames[files[i]] = true err := s.swift.PutObject(s.containerName, files[i], []byte(data)) c.Check(err, gc.IsNil) } items, err := s.swift.List(s.containerName, "", "", "", 0) c.Check(err, gc.IsNil) c.Check(len(items), gc.Equals, len(files)) for _, item := range items { c.Check(fileNames[item.Name], gc.Equals, true) } for i := 0; i < len(files); i++ { s.swift.DeleteObject(s.containerName, files[i]) } } func (s *LiveTests) TestURL(c *gc.C) { object := "test_obj1" data := "...some data..." err := s.swift.PutObject(s.containerName, object, []byte(data)) c.Check(err, gc.IsNil) url, err := s.swift.URL(s.containerName, object) c.Check(err, gc.IsNil) httpClient := http.DefaultClient req, err := http.NewRequest("GET", url, nil) req.Header.Add("X-Auth-Token", s.client.Token()) c.Check(err, gc.IsNil) resp, err := httpClient.Do(req) defer resp.Body.Close() c.Check(err, gc.IsNil) c.Check(resp.StatusCode, gc.Equals, http.StatusOK) objdata, err := ioutil.ReadAll(resp.Body) c.Check(err, gc.IsNil) c.Check(string(objdata), gc.Equals, data) err = s.swift.DeleteObject(s.containerName, object) c.Assert(err, gc.IsNil) } type LiveTestsPublicContainer struct { cred *identity.Credentials client client.AuthenticatingClient publicClient client.Client swift *swift.Client publicSwift *swift.Client containerName string } func (s *LiveTestsPublicContainer) SetUpSuite(c *gc.C) { s.containerName = "test_container" + randomName() s.client = client.NewClient(s.cred, identity.AuthUserPass, nil) s.swift = swift.New(s.client) } func (s *LiveTestsPublicContainer) TearDownSuite(c *gc.C) { // noop, called by local test suite. } func (s *LiveTestsPublicContainer) SetUpTest(c *gc.C) { err := s.client.Authenticate() c.Assert(err, gc.IsNil) baseURL, err := s.client.MakeServiceURL("object-store", "", nil) c.Assert(err, gc.IsNil) s.publicClient = client.NewPublicClient(baseURL, nil) s.publicSwift = swift.New(s.publicClient) assertCreateContainer(c, s.containerName, s.swift, swift.PublicRead) } func (s *LiveTestsPublicContainer) TearDownTest(c *gc.C) { err := s.swift.DeleteContainer(s.containerName) c.Check(err, gc.IsNil) } func (s *LiveTestsPublicContainer) TestPublicObjectReader(c *gc.C) { object := "test_obj2" data := "...some data..." err := s.swift.PutReader(s.containerName, object, bytes.NewReader([]byte(data)), int64(len(data))) c.Check(err, gc.IsNil) r, headers, err := s.publicSwift.GetReader(s.containerName, object) c.Check(err, gc.IsNil) readData, err := ioutil.ReadAll(r) c.Check(err, gc.IsNil) r.Close() c.Check(string(readData), gc.Equals, data) err = s.swift.DeleteObject(s.containerName, object) c.Assert(err, gc.IsNil) c.Check(headers.Get("Date"), gc.Not(gc.Equals), "") } func (s *LiveTestsPublicContainer) TestPublicList(c *gc.C) { data := "...some data..." var files []string = make([]string, 2) var fileNames map[string]bool = make(map[string]bool) for i := 0; i < 2; i++ { files[i] = fmt.Sprintf("test_obj%d", i) fileNames[files[i]] = true err := s.swift.PutObject(s.containerName, files[i], []byte(data)) c.Check(err, gc.IsNil) } items, err := s.publicSwift.List(s.containerName, "", "", "", 0) c.Check(err, gc.IsNil) c.Check(len(items), gc.Equals, len(files)) for _, item := range items { c.Check(fileNames[item.Name], gc.Equals, true) } for i := 0; i < len(files); i++ { s.swift.DeleteObject(s.containerName, files[i]) } } func (s *LiveTestsPublicContainer) TestPublicURL(c *gc.C) { object := "test_obj1" data := "...some data..." err := s.swift.PutObject(s.containerName, object, []byte(data)) c.Check(err, gc.IsNil) url, err := s.swift.URL(s.containerName, object) c.Check(err, gc.IsNil) httpClient := http.DefaultClient req, err := http.NewRequest("GET", url, nil) c.Check(err, gc.IsNil) resp, err := httpClient.Do(req) defer resp.Body.Close() c.Check(err, gc.IsNil) c.Check(resp.StatusCode, gc.Equals, http.StatusOK) objdata, err := ioutil.ReadAll(resp.Body) c.Check(err, gc.IsNil) c.Check(string(objdata), gc.Equals, data) err = s.swift.DeleteObject(s.containerName, object) c.Assert(err, gc.IsNil) } func (s *LiveTests) TestHeadObject(c *gc.C) { object := "test_obj2" data := "...some data..." err := s.swift.PutReader(s.containerName, object, bytes.NewReader([]byte(data)), int64(len(data))) c.Check(err, gc.IsNil) headers, err := s.swift.HeadObject(s.containerName, object) c.Check(err, gc.IsNil) err = s.swift.DeleteObject(s.containerName, object) c.Assert(err, gc.IsNil) c.Check(headers.Get("Date"), gc.Not(gc.Equals), "") } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/swift/local_test.go000066400000000000000000000032661414605405700237370ustar00rootroot00000000000000package swift_test import ( gc "gopkg.in/check.v1" "gopkg.in/goose.v1/identity" "gopkg.in/goose.v1/testing/httpsuite" "gopkg.in/goose.v1/testservices/openstackservice" ) func registerLocalTests() { gc.Suite(&localLiveSuite{}) } // localLiveSuite runs tests from LiveTests using a fake // swift server that runs within the test process itself. type localLiveSuite struct { LiveTests LiveTestsPublicContainer // The following attributes are for using testing doubles. httpsuite.HTTPSuite openstack *openstackservice.Openstack } func (s *localLiveSuite) SetUpSuite(c *gc.C) { c.Logf("Using identity and swift service test doubles") s.HTTPSuite.SetUpSuite(c) // Set up an Openstack service. s.LiveTests.cred = &identity.Credentials{ URL: s.Server.URL, User: "fred", Secrets: "secret", Region: "some region", TenantName: "tenant", } s.LiveTestsPublicContainer.cred = s.LiveTests.cred var logMsg []string s.openstack, logMsg = openstackservice.New(s.LiveTests.cred, identity.AuthUserPass, false) for _, msg := range logMsg { c.Logf(msg) } s.openstack.SetupHTTP(nil) s.LiveTests.SetUpSuite(c) s.LiveTestsPublicContainer.SetUpSuite(c) } func (s *localLiveSuite) TearDownSuite(c *gc.C) { s.LiveTests.TearDownSuite(c) s.LiveTestsPublicContainer.TearDownSuite(c) s.HTTPSuite.TearDownSuite(c) } func (s *localLiveSuite) SetUpTest(c *gc.C) { s.HTTPSuite.SetUpTest(c) s.LiveTests.SetUpTest(c) s.LiveTestsPublicContainer.SetUpTest(c) } func (s *localLiveSuite) TearDownTest(c *gc.C) { s.LiveTests.TearDownTest(c) s.LiveTestsPublicContainer.TearDownTest(c) s.HTTPSuite.TearDownTest(c) } // Additional tests to be run against the service double only go here. golang-gopkg-goose.v1-0.0~git20170406.3228e4f/swift/swift.go000066400000000000000000000154271414605405700227440ustar00rootroot00000000000000// goose/swift - Go package to interact with OpenStack Object-Storage (Swift) API. // See http://docs.openstack.org/api/openstack-object-storage/1.0/content/. package swift import ( "bytes" "fmt" "io" "io/ioutil" "net/http" "net/url" "time" "gopkg.in/goose.v1/client" "gopkg.in/goose.v1/errors" goosehttp "gopkg.in/goose.v1/http" ) // Client provides a means to access the OpenStack Object Storage Service. type Client struct { client client.Client } // New creates a new Client. func New(client client.Client) *Client { return &Client{client} } type ACL string const ( Private = ACL("") PublicRead = ACL(".r:*,.rlistings") ) // CreateContainer creates a container with the given name. func (c *Client) CreateContainer(containerName string, acl ACL) error { // [sodre]: Due to a possible bug in ceph-radosgw, we need to split the // creation of the bucket and the changing its ACL. requestData := goosehttp.RequestData{ExpectedStatus: []int{http.StatusAccepted, http.StatusCreated}} err := c.client.SendRequest(client.PUT, "object-store", "", containerName, &requestData) if err != nil { err = maybeNotFound(err, "failed to create container: %s", containerName) return err } // Normally accessing a container or objects within requires a token // for the tenant. Setting an ACL using the X-Container-Read header // can be used to allow unauthenticated HTTP access. headers := make(http.Header) headers.Add("X-Container-Read", string(acl)) requestData = goosehttp.RequestData{ReqHeaders: headers, ExpectedStatus: []int{http.StatusAccepted, http.StatusNoContent}} err = c.client.SendRequest(client.POST, "object-store", "", containerName, &requestData) if err != nil { err = maybeNotFound(err, "failed to update container read acl: %s", containerName) } return err } // DeleteContainer deletes the specified container. func (c *Client) DeleteContainer(containerName string) error { requestData := goosehttp.RequestData{ExpectedStatus: []int{http.StatusNoContent}} err := c.client.SendRequest(client.DELETE, "object-store", "", containerName, &requestData) if err != nil { err = maybeNotFound(err, "failed to delete container: %s", containerName) } return err } func (c *Client) touchObject(requestData *goosehttp.RequestData, op, containerName, objectName string) error { path := fmt.Sprintf("%s/%s", containerName, objectName) err := c.client.SendRequest(op, "object-store", "", path, requestData) if err != nil { err = maybeNotFound(err, "failed to %s object %s from container %s", op, objectName, containerName) } return err } // HeadObject retrieves object metadata and other standard HTTP headers. func (c *Client) HeadObject(containerName, objectName string) (http.Header, error) { requestData := goosehttp.RequestData{} err := c.touchObject(&requestData, client.HEAD, containerName, objectName) return requestData.RespHeaders, err } // GetObject retrieves the specified object's data. func (c *Client) GetObject(containerName, objectName string) (obj []byte, err error) { rc, _, err := c.GetReader(containerName, objectName) if err != nil { return } defer rc.Close() return ioutil.ReadAll(rc) } // The following defines a ReadCloser implementation which reads no data. // It is used instead of returning a nil pointer, which is the same as http.Request.Body. var emptyReadCloser noData type noData struct { io.ReadCloser } // GetObject retrieves the specified object's data. func (c *Client) GetReader(containerName, objectName string) (io.ReadCloser, http.Header, error) { requestData := goosehttp.RequestData{RespReader: &emptyReadCloser} err := c.touchObject(&requestData, client.GET, containerName, objectName) return requestData.RespReader, requestData.RespHeaders, err } // DeleteObject removes an object from the storage system permanently. func (c *Client) DeleteObject(containerName, objectName string) error { requestData := goosehttp.RequestData{ExpectedStatus: []int{http.StatusNoContent}} err := c.touchObject(&requestData, client.DELETE, containerName, objectName) return err } // PutObject writes, or overwrites, an object's content and metadata. func (c *Client) PutObject(containerName, objectName string, data []byte) error { r := bytes.NewReader(data) return c.PutReader(containerName, objectName, r, int64(len(data))) } // PutReader writes, or overwrites, an object's content and metadata. func (c *Client) PutReader(containerName, objectName string, r io.Reader, length int64) error { requestData := goosehttp.RequestData{ReqReader: r, ReqLength: int(length), ExpectedStatus: []int{http.StatusCreated}} err := c.touchObject(&requestData, client.PUT, containerName, objectName) return err } // ContainerContents describes a single container and its contents. type ContainerContents struct { Name string `json:"name"` Hash string `json:"hash"` LengthBytes int `json:"bytes"` ContentType string `json:"content_type"` LastModified string `json:"last_modified"` } // GetObject retrieves the specified object's data. func (c *Client) List(containerName, prefix, delim, marker string, limit int) (contents []ContainerContents, err error) { params := make(url.Values) params.Add("prefix", prefix) params.Add("delimiter", delim) params.Add("marker", marker) params.Add("format", "json") if limit > 0 { params.Add("limit", fmt.Sprintf("%d", limit)) } requestData := goosehttp.RequestData{Params: ¶ms, RespValue: &contents} err = c.client.SendRequest(client.GET, "object-store", "", containerName, &requestData) if err != nil { err = errors.Newf(err, "failed to list contents of container: %s", containerName) } return } // URL returns a non-signed URL that allows retrieving the object at path. // It only works if the object is publicly readable (see SignedURL). func (c *Client) URL(containerName, file string) (string, error) { return c.client.MakeServiceURL("object-store", "", []string{containerName, file}) } // SignedURL returns a signed URL that allows anyone holding the URL // to retrieve the object at path. The signature is valid until expires. func (c *Client) SignedURL(containerName, file string, expires time.Time) (string, error) { // expiresUnix := expires.Unix() // TODO(wallyworld) - 2013-02-11 bug=1121677 // retrieve the signed URL, for now just return the public URL rawURL, err := c.URL(containerName, file) if err != nil { return "", err } return rawURL, nil } func maybeNotFound(err error, format string, arg ...interface{}) error { if !errors.IsNotFound(err) { if error, ok := err.(*goosehttp.HttpError); ok { // The OpenStack API says that attempts to operate on non existent containers or objects return a status code // of 412 (StatusPreconditionFailed). if error.StatusCode == http.StatusPreconditionFailed { err = errors.NewNotFoundf(err, "", format, arg...) } } } return errors.Newf(err, format, arg...) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/swift/swift_test.go000066400000000000000000000006651414605405700240010ustar00rootroot00000000000000package swift_test import ( "flag" "testing" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/identity" ) var live = flag.Bool("live", false, "Include live OpenStack (Canonistack) tests") func Test(t *testing.T) { if *live { cred, err := identity.CompleteCredentialsFromEnv() if err != nil { t.Fatalf("Error setting up test suite: %s", err.Error()) } registerOpenStackTests(cred) } registerLocalTests() gc.TestingT(t) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/sync/000077500000000000000000000000001414605405700210705ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/sync/timeout.go000066400000000000000000000005341414605405700231070ustar00rootroot00000000000000package sync import ( "time" ) // RunWithTimeout runs the specified function and returns true if it completes before timeout, else false. func RunWithTimeout(timeout time.Duration, f func()) bool { ch := make(chan struct{}) go func() { f() close(ch) }() select { case <-ch: return true case <-time.After(timeout): } return false } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/sync/timeout_test.go000066400000000000000000000007501414605405700241460ustar00rootroot00000000000000package sync import ( "testing" "time" ) func TestRunSuccess(t *testing.T) { timeout := 1 * time.Millisecond var ranOk bool ok := RunWithTimeout(timeout, func() { ranOk = true }) if !ok || !ranOk { t.Fail() } } func TestRunTimeout(t *testing.T) { timeout := 1 * time.Millisecond var ranOk bool sig := make(chan struct{}) ok := RunWithTimeout(timeout, func() { // Block until we timeout. <-sig ranOk = true }) sig <- struct{}{} if ok || ranOk { t.Fail() } } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/test.py000077500000000000000000000156441414605405700214620ustar00rootroot00000000000000#!/usr/bin/env python import os import subprocess import sys KNOWN_LIVE_SUITES = [ 'client', 'glance', 'identity', 'nova', 'neutron', 'swift', ] def ensure_tarmac_log_dir(): """Hack-around tarmac not creating its own log directory.""" try: os.makedirs(os.path.expanduser("~/logs/")) except OSError: # Could be already exists, or cannot create, either way, just continue pass def create_tarmac_repository(): """Try to ensure a shared repository for the code.""" try: from bzrlib import ( branch, controldir, errors, transport, repository, reconfigure, ) except ImportError: sys.stderr.write('Could not import bzrlib to ensure a repository\n') return try: b, _ = branch.Branch.open_containing('.') except: sys.stderr.write('Could not open local branch\n') return # By the time we get here, we've already branched everything from # launchpad. So if we aren't in a shared repository, we create one, and # fetch all the data into it, so it doesn't have to be fetched again. if b.repository.is_shared(): return pwd = os.getcwd() expected_dir = 'src/github.com/' offset = pwd.rfind(expected_dir) if offset == -1: sys.stderr.write('Could not find %r to create a shared repo\n') return path = pwd[:offset+len(expected_dir)] try: repository.Repository.open(path) except (errors.NoRepositoryPresent, errors.NotBranchError): pass # Good, the repo didn't exist else: # We must have already created the repo. return repo_fmt = controldir.format_registry.make_bzrdir('default') trans = transport.get_transport(path) info = repo_fmt.initialize_on_transport_ex(trans, create_prefix=False, make_working_trees=True, shared_repo=True, force_new_repo=True, use_existing_dir=True, repo_format_name=repo_fmt.repository_format.get_format_string()) repo = info[0] sys.stderr.write('Reconfiguring to use a shared repository\n') reconfiguration = reconfigure.Reconfigure.to_use_shared(b.bzrdir) try: reconfiguration.apply(False) except errors.NoRepositoryPresent: sys.stderr.write('tarmac did a lightweight checkout,' ' not fetching into the repo.\n') def ensure_juju_core_dependencies(): """Ensure that juju-core and all dependencies have been installed.""" # Note: This potentially overwrites goose while it is updating the world. # However, if we are targetting the trunk branch of goose, that should have # already been updated to the latest version by tarmac. # I don't quite see a way to reconcile that we want the latest juju-core # and all of the other dependencies, but we don't want to touch goose # itself. One option would be to have a split GOPATH. One installs the # latest juju-core and everything else. The other is where the # goose-under-test resides. So we don't add the goose-under-test to GOPATH, # call "go get", then add it to the GOPATH for the rest of the testing. cmd = ['go', 'get', '-u', '-x', 'github.com/juju/...'] sys.stderr.write('Running: %s\n' % (' '.join(cmd),)) retcode = subprocess.call(cmd) if retcode != 0: sys.stderr.write('WARN: Failed to update github.com/juju\n') def tarmac_setup(opts): """Do all the bits of setup that need to happen for the tarmac bot.""" ensure_tarmac_log_dir() create_tarmac_repository() def setup_gopath(): pwd = os.getcwd() if sys.platform == 'win32': pwd = pwd.replace('\\', '/') offset = pwd.rfind('src/gopkg.in/goose.v1') if offset == -1: sys.stderr.write('Could not find "src/gopkg.in/goose.v1" in cwd: %s\n' % (pwd,)) sys.stderr.write('Unable to automatically set GOPATH\n') return add_gopath = pwd[:offset].rstrip('/') gopath = os.environ.get("GOPATH") if gopath: if add_gopath in gopath: return # Put this path first, so we know we are running these tests gopath = add_gopath + os.pathsep + gopath else: gopath = add_gopath sys.stderr.write('Setting GOPATH to: %s\n' % (gopath,)) os.environ['GOPATH'] = gopath def run_cmd(cmd): cmd_str = ' '.join(cmd) sys.stderr.write('Running: %s\n' % (cmd_str,)) retcode = subprocess.call(cmd) if retcode != 0: sys.stderr.write('FAIL: failed running: %s\n' % (cmd_str,)) return retcode def run_go_fmt(opts): return run_cmd(['go', 'fmt', './...']) def run_go_build(opts): return run_cmd(['go', 'build', './...']) def run_go_test(opts): # Note: I wish we could run this with '-check.v' return run_cmd(['go', 'test', './...']) def run_juju_core_tests(opts): """Run the juju-core test suite""" orig_wd = os.getcwd() try: sys.stderr.write('Switching to juju-core\n') os.chdir('../juju-core') retval = run_cmd(['go', 'build', './...']) if retval != 0: return retval return run_cmd(['go', 'test', './...']) finally: os.chdir(orig_wd) def run_live_tests(opts): """Run all of the live tests.""" orig_wd = os.getcwd() final_retcode = 0 for d in KNOWN_LIVE_SUITES: try: cmd = ['go', 'test', '-live', '-check.v'] sys.stderr.write('Running: %s in %s\n' % (' '.join(cmd), d)) os.chdir(d) retcode = subprocess.call(cmd) if retcode != 0: sys.stderr.write('FAIL: Running live tests in %s\n' % (d,)) final_retcode = retcode finally: os.chdir(orig_wd) return final_retcode def main(args): import argparse p = argparse.ArgumentParser(description='Run the goose test suite') p.add_argument('--verbose', action='store_true', help='Be chatty') p.add_argument('--version', action='version', version='%(prog)s 0.1') p.add_argument('--tarmac', action='store_true', help="Pass this if the script is running as the tarmac bot." " This is used for stuff like ensuring repositories and" " logging directories are initialized.") p.add_argument('--juju-core', action='store_true', help="Run the juju-core trunk tests as well as the goose tests.") p.add_argument('--live', action='store_true', help="Run tests against a live service.") opts = p.parse_args(args) setup_gopath() if opts.tarmac: tarmac_setup(opts) to_run = [run_go_fmt, run_go_build, run_go_test] if opts.juju_core: ensure_juju_core_dependencies() to_run.append(run_juju_core_tests) if opts.live: to_run.append(run_live_tests) for func in to_run: retcode = func(opts) if retcode != 0: return retcode if __name__ == '__main__': import sys sys.exit(main(sys.argv[1:])) golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testing/000077500000000000000000000000001414605405700215715ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testing/envsuite/000077500000000000000000000000001414605405700234335ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testing/envsuite/envsuite.go000066400000000000000000000011641414605405700256260ustar00rootroot00000000000000package envsuite // Provides an EnvSuite type which makes sure this test suite gets an isolated // environment settings. Settings will be saved on start and then cleared, and // reset on tear down. import ( "os" "strings" gc "gopkg.in/check.v1" ) type EnvSuite struct { environ []string } func (s *EnvSuite) SetUpSuite(c *gc.C) { s.environ = os.Environ() } func (s *EnvSuite) SetUpTest(c *gc.C) { os.Clearenv() } func (s *EnvSuite) TearDownTest(c *gc.C) { for _, envstring := range s.environ { kv := strings.SplitN(envstring, "=", 2) os.Setenv(kv[0], kv[1]) } } func (s *EnvSuite) TearDownSuite(c *gc.C) { } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testing/envsuite/envsuite_test.go000066400000000000000000000024011414605405700266600ustar00rootroot00000000000000package envsuite import ( "os" "testing" gc "gopkg.in/check.v1" ) type EnvTestSuite struct { EnvSuite } func Test(t *testing.T) { gc.TestingT(t) } var _ = gc.Suite(&EnvTestSuite{}) func (s *EnvTestSuite) TestGrabsCurrentEnvironment(c *gc.C) { envsuite := &EnvSuite{} // EnvTestSuite is an EnvSuite, so we should have already isolated // ourselves from the world. So we set a single env value, and we // assert that SetUpSuite is able to see that. os.Setenv("TEST_KEY", "test-value") envsuite.SetUpSuite(c) c.Assert(envsuite.environ, gc.DeepEquals, []string{"TEST_KEY=test-value"}) } func (s *EnvTestSuite) TestClearsEnvironment(c *gc.C) { envsuite := &EnvSuite{} os.Setenv("TEST_KEY", "test-value") envsuite.SetUpSuite(c) // SetUpTest should reset the current environment back to being // completely empty. envsuite.SetUpTest(c) c.Assert(os.Getenv("TEST_KEY"), gc.Equals, "") c.Assert(os.Environ(), gc.DeepEquals, []string{}) } func (s *EnvTestSuite) TestRestoresEnvironment(c *gc.C) { envsuite := &EnvSuite{} os.Setenv("TEST_KEY", "test-value") envsuite.SetUpSuite(c) envsuite.SetUpTest(c) envsuite.TearDownTest(c) c.Assert(os.Getenv("TEST_KEY"), gc.Equals, "test-value") c.Assert(os.Environ(), gc.DeepEquals, []string{"TEST_KEY=test-value"}) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testing/httpsuite/000077500000000000000000000000001414605405700236225ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testing/httpsuite/httpsuite.go000066400000000000000000000017771414605405700262160ustar00rootroot00000000000000package httpsuite // This package provides an HTTPSuite infrastructure that lets you bring up an // HTTP server. The server will handle requests based on whatever Handlers are // attached to HTTPSuite.Mux. This Mux is reset after every test case, and the // server is shut down at the end of the test suite. import ( "net/http" "net/http/httptest" gc "gopkg.in/check.v1" ) var _ = gc.Suite(&HTTPSuite{}) type HTTPSuite struct { Server *httptest.Server Mux *http.ServeMux oldHandler http.Handler UseTLS bool } func (s *HTTPSuite) SetUpSuite(c *gc.C) { if s.UseTLS { s.Server = httptest.NewTLSServer(nil) } else { s.Server = httptest.NewServer(nil) } } func (s *HTTPSuite) SetUpTest(c *gc.C) { s.oldHandler = s.Server.Config.Handler s.Mux = http.NewServeMux() s.Server.Config.Handler = s.Mux } func (s *HTTPSuite) TearDownTest(c *gc.C) { s.Mux = nil s.Server.Config.Handler = s.oldHandler } func (s *HTTPSuite) TearDownSuite(c *gc.C) { if s.Server != nil { s.Server.Close() } } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testing/httpsuite/httpsuite_test.go000066400000000000000000000035331414605405700272450ustar00rootroot00000000000000package httpsuite import ( "crypto/tls" "crypto/x509" "io/ioutil" "net/http" "net/url" "reflect" "testing" gc "gopkg.in/check.v1" ) type HTTPTestSuite struct { HTTPSuite } type HTTPSTestSuite struct { HTTPSuite } func Test(t *testing.T) { gc.TestingT(t) } var _ = gc.Suite(&HTTPTestSuite{}) var _ = gc.Suite(&HTTPSTestSuite{HTTPSuite{UseTLS: true}}) type HelloHandler struct{} func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(200) w.Write([]byte("Hello World\n")) } func (s *HTTPTestSuite) TestHelloWorld(c *gc.C) { s.Mux.Handle("/", &HelloHandler{}) // fmt.Printf("Running HelloWorld\n") response, err := http.Get(s.Server.URL) c.Check(err, gc.IsNil) content, err := ioutil.ReadAll(response.Body) response.Body.Close() c.Check(err, gc.IsNil) c.Check(response.Status, gc.Equals, "200 OK") c.Check(response.StatusCode, gc.Equals, 200) c.Check(string(content), gc.Equals, "Hello World\n") } func (s *HTTPSTestSuite) TestHelloWorldWithTLS(c *gc.C) { s.Mux.Handle("/", &HelloHandler{}) c.Check(s.Server.URL[:8], gc.Equals, "https://") response, err := http.Get(s.Server.URL) // Default http.Get fails because the cert is self-signed c.Assert(err, gc.NotNil) c.Assert(reflect.TypeOf(err.(*url.Error).Err), gc.Equals, reflect.TypeOf(x509.UnknownAuthorityError{})) // Connect again with a Client that doesn't validate the cert insecureClient := &http.Client{Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}} response, err = insecureClient.Get(s.Server.URL) c.Assert(err, gc.IsNil) content, err := ioutil.ReadAll(response.Body) response.Body.Close() c.Check(err, gc.IsNil) c.Check(response.Status, gc.Equals, "200 OK") c.Check(response.StatusCode, gc.Equals, 200) c.Check(string(content), gc.Equals, "Hello World\n") } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/000077500000000000000000000000001414605405700226375ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/cmd/000077500000000000000000000000001414605405700234025ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/cmd/main.go000066400000000000000000000031351414605405700246570ustar00rootroot00000000000000package main import ( "fmt" "log" "net/http" "strings" "github.com/juju/gnuflag" "gopkg.in/goose.v1/testservices/identityservice" ) type userInfo struct { user, secret string } type userValues struct { users []userInfo } func (uv *userValues) Set(s string) error { vals := strings.Split(s, ":") if len(vals) != 2 { return fmt.Errorf("Invalid --user option, should be: user:secret") } uv.users = append(uv.users, userInfo{ user: vals[0], secret: vals[1], }) return nil } func (uv *userValues) String() string { return fmt.Sprintf("%v", uv.users) } var provider = gnuflag.String("provider", "userpass", "provide the name of the identity service to run") var serveAddr = gnuflag.String("addr", "localhost:8080", "serve the provider on the given address.") var users userValues func init() { gnuflag.Var(&users, "user", "supply to add a user to the identity provider. Can be supplied multiple times. Should be of the form \"user:secret:token\".") } var providerMap = map[string]identityservice.IdentityService{ "legacy": identityservice.NewLegacy(), "userpass": identityservice.NewUserPass(), } func providers() []string { out := make([]string, 0, len(providerMap)) for provider := range providerMap { out = append(out, provider) } return out } func main() { gnuflag.Parse(true) p, ok := providerMap[*provider] if !ok { log.Fatalf("No such provider: %s, pick one of: %v", *provider, providers()) } mux := http.NewServeMux() p.SetupHTTP(mux) for _, u := range users.users { p.AddUser(u.user, u.secret, "tenant", "default") } log.Fatal(http.ListenAndServe(*serveAddr, mux)) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/errors.go000066400000000000000000000135551414605405700245130ustar00rootroot00000000000000package testservices import "fmt" // This map is copied from nova python client // https://github.com/openstack/nova/blob/master/nova/api/openstack/wsgi.py#L1185 var nameReference = map[int]string{ 400: "badRequest", 401: "unauthorized", 403: "forbidden", 404: "itemNotFound", 405: "badMethod", 409: "conflictingRequest", 413: "overLimit", 415: "badMediaType", 429: "overLimit", 501: "notImplemented", 503: "serviceUnavailable", } type ServerError struct { message string code int } func serverErrorf(code int, message string, args ...interface{}) *ServerError { return &ServerError{code: code, message: fmt.Sprintf(message, args...)} } func (n *ServerError) Code() int { return n.code } func (n *ServerError) AsJSON() string { return fmt.Sprintf(`{%q:{"message":%q, "code":%d}}`, n.Name(), n.message, n.code) } func (n *ServerError) Error() string { return fmt.Sprintf("%s: %s", n.Name(), n.message) } func (n *ServerError) Name() string { name, ok := nameReference[n.code] if !ok { return "computeFault" } return name } func NewInternalServerError(message string) *ServerError { return serverErrorf(500, message) } func NewNotFoundError(message string) *ServerError { return serverErrorf(404, message) } func NewNoMoreFloatingIpsError() *ServerError { return serverErrorf(404, "Zero floating ips available") } func NewIPLimitExceededError() *ServerError { return serverErrorf(413, "Maximum number of floating ips exceeded") } func NewRateLimitExceededError() *ServerError { // This is an undocumented error return serverErrorf(413, "Retry limit exceeded") } func NewAvailabilityZoneIsNotAvailableError() *ServerError { return serverErrorf(400, "The requested availability zone is not available") } func NewAddFlavorError(id string) *ServerError { return serverErrorf(409, "A flavor with id %q already exists", id) } func NewNoSuchFlavorError(id string) *ServerError { return serverErrorf(404, "No such flavor %q", id) } func NewServerByIDNotFoundError(id string) *ServerError { return serverErrorf(404, "No such server %q", id) } func NewServerByNameNotFoundError(name string) *ServerError { return serverErrorf(404, "No such server named %q", name) } func NewServerAlreadyExistsError(id string) *ServerError { return serverErrorf(409, "A server with id %q already exists", id) } func NewSecurityGroupAlreadyExistsError(id string) *ServerError { return serverErrorf(409, "A security group with id %s already exists", id) } func NewSecurityGroupByIDNotFoundError(groupId string) *ServerError { return serverErrorf(404, "No such security group %s", groupId) } func NewSecurityGroupByNameNotFoundError(name string) *ServerError { return serverErrorf(404, "No such security group named %q", name) } func NewSecurityGroupRuleAlreadyExistsError(id string) *ServerError { return serverErrorf(409, "A security group rule with id %s already exists", id) } func NewNeutronSecurityGroupRuleAlreadyExistsError(parentId string) *ServerError { return serverErrorf(409, "Security group rule already exists. Group id is %s.", parentId) } func NewCannotAddTwiceRuleToGroupError(ruleId, groupId string) *ServerError { return serverErrorf(409, "Cannot add twice rule %s to security group %s", ruleId, groupId) } func NewUnknownSecurityGroupError(groupId string) *ServerError { return serverErrorf(409, "Unknown source security group %s", groupId) } func NewSecurityGroupRuleNotFoundError(ruleId string) *ServerError { return serverErrorf(404, "No such security group rule %s", ruleId) } func NewInvalidDirectionSecurityGroupError(direction string) *ServerError { return serverErrorf(400, "Invalid input for direction. Reason: %s is not ingress or egress.", direction) } func NewSecurityGroupRuleInvalidEthernetType(ethernetType string) *ServerError { return serverErrorf(400, "Invalid input for ethertype. Reason: %s is not '', 'IPv4' or 'IPv6'.", ethernetType) } func NewSecurityGroupRuleParameterConflict(param1 string, value1 string, param2 string, value2 string) *ServerError { return serverErrorf(400, "Conflicting value %s %s for %s %s", param1, value1, param2, value2) } func NewSecurityGroupRuleInvalidCIDR(cidr string) *ServerError { return serverErrorf(400, "Invalid CIDR %s given as IP prefix.", cidr) } func NewServerBelongsToGroupError(serverId, groupId string) *ServerError { return serverErrorf(409, "Server %q already belongs to group %s", serverId, groupId) } func NewServerDoesNotBelongToGroupsError(serverId string) *ServerError { return serverErrorf(400, "Server %q does not belong to any groups", serverId) } func NewServerDoesNotBelongToGroupError(serverId, groupId string) *ServerError { return serverErrorf(400, "Server %q does not belong to group %s", serverId, groupId) } func NewFloatingIPExistsError(ipID string) *ServerError { return serverErrorf(409, "A floating IP with id %s already exists", ipID) } func NewFloatingIPNotFoundError(address string) *ServerError { return serverErrorf(404, "No such floating IP %q", address) } func NewServerHasFloatingIPError(serverId, ipId string) *ServerError { return serverErrorf(409, "Server %q already has floating IP %s", serverId, ipId) } func NewNoFloatingIPsToRemoveError(serverId string) *ServerError { return serverErrorf(409, "Server %q does not have any floating IPs to remove", serverId) } func NewNoFloatingIPsError(serverId, ipId string) *ServerError { return serverErrorf(404, "Server %q does not have floating IP %s", serverId, ipId) } func NewNetworkNotFoundError(network string) *ServerError { return serverErrorf(404, "No such network %q", network) } func NewNetworkAlreadyExistsError(id string) *ServerError { return serverErrorf(409, "A network with id %q already exists", id) } func NewSubnetNotFoundError(subnet string) *ServerError { return serverErrorf(404, "No such subnet %q", subnet) } func NewSubnetAlreadyExistsError(id string) *ServerError { return serverErrorf(409, "A subnet with id %q already exists", id) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/errors_test.go000066400000000000000000000011341414605405700255400ustar00rootroot00000000000000package testservices import ( "testing" gc "gopkg.in/check.v1" ) func Test(t *testing.T) { gc.TestingT(t) } type ErrorsSuite struct { } var _ = gc.Suite(&ErrorsSuite{}) func (s *ErrorsSuite) TestServerErrorMessage(c *gc.C) { err := &ServerError{ message: "Instance could not be found", code: 404, } c.Assert(err, gc.ErrorMatches, "itemNotFound: Instance could not be found") } func (s *ErrorsSuite) TestServerUnknownErrcode(c *gc.C) { err := &ServerError{ message: "Impossible http code.", code: 999, } c.Assert(err, gc.ErrorMatches, "computeFault: Impossible http code.") } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/hook/000077500000000000000000000000001414605405700235775ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/hook/service.go000066400000000000000000000052151414605405700255710ustar00rootroot00000000000000package hook type TestService struct { ServiceControl // Hooks to run when specified control points are reached in the service business logic. ControlHooks map[string]ControlProcessor } // ControlProcessor defines a function that is run when a specified control point is reached in the service // business logic. The function receives the service instance so internal state can be inspected, plus for any // arguments passed to the currently executing service function. type ControlProcessor func(sc ServiceControl, args ...interface{}) error // ControlHookCleanup defines a function used to remove a control hook. type ControlHookCleanup func() // ServiceControl instances allow hooks to be registered for execution at the specified point of execution. // The control point name can be a function name or a logical execution point meaningful to the service. // If name is "", the hook for the currently executing function is executed. // Returns a function which can be used to remove the hook. type ServiceControl interface { RegisterControlPoint(name string, controller ControlProcessor) ControlHookCleanup } // ProcessControlHook retrieves the ControlProcessor for the specified hook name and runs it, returning any error. // Use it like this to invoke a hook registered for some arbitrary control point: // if err := n.ProcessControlHook("foobar", , , ); err != nil { // return err // } func (s *TestService) ProcessControlHook(hookName string, sc ServiceControl, args ...interface{}) error { if s.ControlHooks == nil { return nil } if hook, ok := s.ControlHooks[hookName]; ok { return hook(sc, args...) } return nil } // ProcessFunctionHook runs the ControlProcessor for the current function, returning any error. // Use it like this: // if err := n.ProcessFunctionHook(, , ); err != nil { // return err // } func (s *TestService) ProcessFunctionHook(sc ServiceControl, args ...interface{}) error { hookName := s.currentServiceMethodName() return s.ProcessControlHook(hookName, sc, args...) } // RegisterControlPoint assigns the specified controller to the named hook. If nil, any existing controller for the // hook is removed. // hookName is the name of a function on the service or some arbitrarily named control point. func (s *TestService) RegisterControlPoint(hookName string, controller ControlProcessor) ControlHookCleanup { if s.ControlHooks == nil { s.ControlHooks = make(map[string]ControlProcessor) } if controller == nil { delete(s.ControlHooks, hookName) } else { s.ControlHooks[hookName] = controller } return func() { s.RegisterControlPoint(hookName, nil) } } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/hook/service_gc.go000066400000000000000000000010401414605405700262320ustar00rootroot00000000000000// +build !gccgo package hook import ( "runtime" "strings" ) // currentServiceMethodName returns the method executing on the service when ProcessControlHook was invoked. func (s *TestService) currentServiceMethodName() string { pc, _, _, ok := runtime.Caller(2) if !ok { panic("current method name cannot be found") } return unqualifiedMethodName(pc) } func unqualifiedMethodName(pc uintptr) string { f := runtime.FuncForPC(pc) fullName := f.Name() nameParts := strings.Split(fullName, ".") return nameParts[len(nameParts)-1] } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/hook/service_gccgo.go000066400000000000000000000040701414605405700267310ustar00rootroot00000000000000// +build gccgo package hook import ( "runtime" "strings" ) // callerDepth defines the number of stack frames to skip during // currentServiceMethodName. This value differs for various gccgo // versions. var callerDepth int // namePartsPos defines the position within the raw method name, deliniated by periods. var namePartsPos = -1 // will panic if we cannot determine the position. type inner struct{} func (i *inner) m() { for callerDepth = 1; ; callerDepth++ { pc, _, _, ok := runtime.Caller(callerDepth) if !ok { panic("current method name cannot be found") } if name := runtime.FuncForPC(pc).Name(); name == "hook.setCallerDepth" { for i, s := range strings.Split(name, ".") { if s == "setCallerDepth" { namePartsPos = i break } } return } } } type outer struct { inner } func setCallerDepth0() { var o outer o.m() } func setCallerDepth() { setCallerDepth0() } func init() { setCallerDepth() println(callerDepth) } // currentServiceMethodName returns the method executing on the service when ProcessControlHook was invoked. func (s *TestService) currentServiceMethodName() string { // We have to go deeper into the stack with gccgo because in a situation like: // type Inner { } // func (i *Inner) meth {} // type Outer { Inner } // o = &Outer{} // o.meth() // gccgo generates a method called "meth" on *Outer, and this shows up // on the stack as seen by runtime.Caller (this might be a gccgo bug). pc, _, _, ok := runtime.Caller(callerDepth) if !ok { panic("current method name cannot be found") } return unqualifiedMethodName(pc) } func unqualifiedMethodName(pc uintptr) string { f := runtime.FuncForPC(pc) fullName := f.Name() // This is very fragile. fullName will be something like: // launchpad.net_goose_testservices_novaservice.removeServer.pN49_launchpad.net_goose_testservices_novaservice.Nova // so if the number of dots in the full package path changes, // We try to figure sniff this value at the top, but it may not work. nameParts := strings.Split(fullName, ".") return nameParts[namePartsPos] } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/hook/service_test.go000066400000000000000000000047461414605405700266400ustar00rootroot00000000000000package hook import ( "fmt" "testing" gc "gopkg.in/check.v1" ) func Test(t *testing.T) { gc.TestingT(t) } var _ = gc.Suite(&ServiceSuite{}) type ServiceSuite struct { ts *testService } func (s *ServiceSuite) SetUpTest(c *gc.C) { s.ts = newTestService() // This hook is called based on the function name. s.ts.RegisterControlPoint("foo", functionControlHook) // This hook is called based on a user specified hook name. s.ts.RegisterControlPoint("foobar", namedControlHook) } type testService struct { TestService label string } func newTestService() *testService { return &testService{ TestService: TestService{ ControlHooks: make(map[string]ControlProcessor), }, } } func functionControlHook(s ServiceControl, args ...interface{}) error { label := args[0].(string) returnError := args[1].(bool) if returnError { return fmt.Errorf("An error occurred") } s.(*testService).label = label return nil } func namedControlHook(s ServiceControl, args ...interface{}) error { s.(*testService).label = "foobar" return nil } func (s *testService) foo(label string, returnError bool) error { if err := s.ProcessFunctionHook(s, label, returnError); err != nil { return err } return nil } func (s *testService) bar() error { if err := s.ProcessControlHook("foobar", s); err != nil { return err } return nil } func (s *ServiceSuite) TestFunctionHookNoError(c *gc.C) { err := s.ts.foo("success", false) c.Assert(err, gc.IsNil) c.Assert(s.ts.label, gc.Equals, "success") } func (s *ServiceSuite) TestHookWithError(c *gc.C) { err := s.ts.foo("success", true) c.Assert(err, gc.Not(gc.IsNil)) c.Assert(s.ts.label, gc.Equals, "") } func (s *ServiceSuite) TestNamedHook(c *gc.C) { err := s.ts.bar() c.Assert(err, gc.IsNil) c.Assert(s.ts.label, gc.Equals, "foobar") } func (s *ServiceSuite) TestHookCleanup(c *gc.C) { // Manually delete the existing control point. s.ts.RegisterControlPoint("foo", nil) // Register a new hook and ensure it works. cleanup := s.ts.RegisterControlPoint("foo", functionControlHook) err := s.ts.foo("cleanuptest", false) c.Assert(err, gc.IsNil) c.Assert(s.ts.label, gc.Equals, "cleanuptest") // Use the cleanup func to remove the hook and check the result. cleanup() err = s.ts.foo("again", false) c.Assert(err, gc.IsNil) c.Assert(s.ts.label, gc.Equals, "cleanuptest") // Ensure that only the specified hook was removed and the other remaining one still works. err = s.ts.bar() c.Assert(err, gc.IsNil) c.Assert(s.ts.label, gc.Equals, "foobar") } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/identityservice/000077500000000000000000000000001414605405700260515ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/identityservice/identityservice.go000066400000000000000000000012771414605405700316210ustar00rootroot00000000000000package identityservice import "net/http" // An IdentityService provides user authentication for an Openstack instance. type IdentityService interface { AddUser(user, secret, tenant, authDomain string) *UserInfo FindUser(token string) (*UserInfo, error) RegisterServiceProvider(name, serviceType string, serviceProvider ServiceProvider) AddService(service Service) SetupHTTP(mux *http.ServeMux) } // Service wraps two possible Service versions type Service struct { V2 V2Service V3 V3Service } // ServiceProvider is an Openstack module which has service endpoints. type ServiceProvider interface { // For Keystone V2 Endpoints() []Endpoint // For Keystone V3 V3Endpoints() []V3Endpoint } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/identityservice/keypair.go000066400000000000000000000065151414605405700300530ustar00rootroot00000000000000package identityservice import ( "encoding/json" "fmt" "io/ioutil" "net/http" "gopkg.in/goose.v1/testservices/hook" ) // Implement the v2 Key Pair form of identity based on Keystone type KeyPairRequest struct { Auth struct { ApiAccessKeyCredentials struct { AccessKey string `json:"accessKey"` SecretKey string `json:"secretKey"` } `json:"apiAccessKeyCredentials"` TenantName string `json:"tenantName"` } `json:"auth"` } type KeyPair struct { hook.TestService Users services []V2Service } func NewKeyPair() *KeyPair { return &KeyPair{ Users: Users{ users: make(map[string]UserInfo), tenants: make(map[string]string), }, } } func (u *KeyPair) RegisterServiceProvider(name, serviceType string, serviceProvider ServiceProvider) { service := V2Service{name, serviceType, serviceProvider.Endpoints()} u.AddService(Service{V2: service}) } func (u *KeyPair) AddService(service Service) { u.services = append(u.services, service.V2) } func (u *KeyPair) ReturnFailure(w http.ResponseWriter, status int, message string) { e := ErrorWrapper{ Error: ErrorResponse{ Message: message, Code: status, Title: http.StatusText(status), }, } if content, err := json.Marshal(e); err != nil { w.Header().Set("Content-Length", fmt.Sprintf("%d", len(internalError))) w.WriteHeader(http.StatusInternalServerError) w.Write(internalError) } else { w.Header().Set("Content-Length", fmt.Sprintf("%d", len(content))) w.WriteHeader(status) w.Write(content) } } func (u *KeyPair) ServeHTTP(w http.ResponseWriter, r *http.Request) { var req KeyPairRequest // Testing against Canonistack, all responses are application/json, even failures w.Header().Set("Content-Type", "application/json") if r.Header.Get("Content-Type") != "application/json" { u.ReturnFailure(w, http.StatusBadRequest, notJSON) return } if content, err := ioutil.ReadAll(r.Body); err != nil { w.WriteHeader(http.StatusBadRequest) return } else { if err := json.Unmarshal(content, &req); err != nil { u.ReturnFailure(w, http.StatusBadRequest, notJSON) return } } userInfo, errmsg := u.authenticate(req.Auth.ApiAccessKeyCredentials.AccessKey, req.Auth.ApiAccessKeyCredentials.SecretKey, "default") if errmsg != "" { u.ReturnFailure(w, http.StatusUnauthorized, errmsg) return } res, err := u.generateAccessResponse(userInfo) if err != nil { u.ReturnFailure(w, http.StatusInternalServerError, err.Error()) return } content, err := json.Marshal(res) if err != nil { u.ReturnFailure(w, http.StatusInternalServerError, err.Error()) return } w.WriteHeader(http.StatusOK) w.Write(content) } func (u *KeyPair) generateAccessResponse(userInfo *UserInfo) (*AccessResponse, error) { res := AccessResponse{} // We pre-populate the response with genuine entries so that it looks sane. if err := json.Unmarshal([]byte(exampleResponse), &res); err != nil { return nil, err } res.Access.ServiceCatalog = u.services res.Access.Token.Id = userInfo.Token res.Access.Token.Tenant.Id = userInfo.TenantId res.Access.User.Id = userInfo.Id if err := u.ProcessControlHook("authorisation", u, &res, userInfo); err != nil { return nil, err } return &res, nil } // setupHTTP attaches all the needed handlers to provide the HTTP API. func (u *KeyPair) SetupHTTP(mux *http.ServeMux) { mux.Handle("/tokens", u) } func (u *KeyPair) Stop() { // noop } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/identityservice/keypair_test.go000066400000000000000000000072641414605405700311140ustar00rootroot00000000000000package identityservice import ( "encoding/json" "fmt" "io/ioutil" "net/http" "strings" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/testing/httpsuite" ) type KeyPairSuite struct { httpsuite.HTTPSuite } var _ = gc.Suite(&KeyPairSuite{}) func makeKeyPair(user, secret string) (identity *KeyPair) { identity = NewKeyPair() // Ensure that it conforms to the interface var _ IdentityService = identity if user != "" { identity.AddUser(user, secret, "tenant", "default") } return } func (s *KeyPairSuite) setupKeyPair(user, secret string) { var identity *KeyPair identity = makeKeyPair(user, secret) identity.SetupHTTP(s.Mux) return } func (s *KeyPairSuite) setupKeyPairWithServices(user, secret string, services []Service) { var identity *KeyPair identity = makeKeyPair(user, secret) for _, service := range services { identity.AddService(service) } identity.SetupHTTP(s.Mux) return } const authKeyPairTemplate = `{ "auth": { "tenantName": "tenant-something", "apiAccessKeyCredentials": { "accessKey": "%s", "secretKey": "%s" } } }` func keyPairAuthRequest(URL, access, secret string) (*http.Response, error) { client := http.DefaultClient body := strings.NewReader(fmt.Sprintf(authKeyPairTemplate, access, secret)) request, err := http.NewRequest("POST", URL+"/tokens", body) request.Header.Set("Content-Type", "application/json") if err != nil { return nil, err } return client.Do(request) } func (s *KeyPairSuite) TestNotJSON(c *gc.C) { // We do everything in keyPairAuthRequest, except set the Content-Type s.setupKeyPair("user", "secret") client := http.DefaultClient body := strings.NewReader(fmt.Sprintf(authTemplate, "user", "secret")) request, err := http.NewRequest("POST", s.Server.URL+"/tokens", body) c.Assert(err, gc.IsNil) res, err := client.Do(request) c.Assert(err, gc.IsNil) defer res.Body.Close() CheckErrorResponse(c, res, http.StatusBadRequest, notJSON) } func (s *KeyPairSuite) TestBadJSON(c *gc.C) { // We do everything in keyPairAuthRequest, except set the Content-Type s.setupKeyPair("user", "secret") res, err := keyPairAuthRequest(s.Server.URL, `garbage"in`, "secret") c.Assert(err, gc.IsNil) defer res.Body.Close() CheckErrorResponse(c, res, http.StatusBadRequest, notJSON) } func (s *KeyPairSuite) TestNoSuchUser(c *gc.C) { s.setupKeyPair("user", "secret") res, err := keyPairAuthRequest(s.Server.URL, "not-user", "secret") c.Assert(err, gc.IsNil) defer res.Body.Close() CheckErrorResponse(c, res, http.StatusUnauthorized, notAuthorized) } func (s *KeyPairSuite) TestBadPassword(c *gc.C) { s.setupKeyPair("user", "secret") res, err := keyPairAuthRequest(s.Server.URL, "user", "not-secret") c.Assert(err, gc.IsNil) defer res.Body.Close() CheckErrorResponse(c, res, http.StatusUnauthorized, invalidUser) } func (s *KeyPairSuite) TestValidAuthorization(c *gc.C) { compute_url := "http://testing.invalid/compute" s.setupKeyPairWithServices("user", "secret", []Service{ {V2: V2Service{"nova", "compute", []Endpoint{ {PublicURL: compute_url}, }}}}) res, err := keyPairAuthRequest(s.Server.URL, "user", "secret") c.Assert(err, gc.IsNil) defer res.Body.Close() c.Check(res.StatusCode, gc.Equals, http.StatusOK) c.Check(res.Header.Get("Content-Type"), gc.Equals, "application/json") content, err := ioutil.ReadAll(res.Body) c.Assert(err, gc.IsNil) var response AccessResponse err = json.Unmarshal(content, &response) c.Assert(err, gc.IsNil) c.Check(response.Access.Token.Id, gc.NotNil) novaURL := "" for _, service := range response.Access.ServiceCatalog { if service.Type == "compute" { novaURL = service.Endpoints[0].PublicURL break } } c.Assert(novaURL, gc.Equals, compute_url) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/identityservice/legacy.go000066400000000000000000000026651414605405700276550ustar00rootroot00000000000000package identityservice import ( "net/http" ) type Legacy struct { Users managementURL string } func NewLegacy() *Legacy { service := &Legacy{} service.users = make(map[string]UserInfo) service.tenants = make(map[string]string) return service } func (lis *Legacy) RegisterServiceProvider(name, serviceType string, serviceProvider ServiceProvider) { // NOOP for legacy identity service. } func (lis *Legacy) AddService(service Service) { // NOOP for legacy identity service. } func (lis *Legacy) SetManagementURL(URL string) { lis.managementURL = URL } // setupHTTP attaches all the needed handlers to provide the HTTP API. func (lis *Legacy) SetupHTTP(mux *http.ServeMux) { mux.Handle("/", lis) } func (lis *Legacy) Stop() { // NOOP for legacy identity service. } func (lis *Legacy) ServeHTTP(w http.ResponseWriter, r *http.Request) { username := r.Header.Get("X-Auth-User") userInfo, ok := lis.users[username] if !ok { w.WriteHeader(http.StatusUnauthorized) return } auth_key := r.Header.Get("X-Auth-Key") if auth_key != userInfo.secret { w.WriteHeader(http.StatusUnauthorized) return } if userInfo.Token == "" { userInfo.Token = randomHexToken() lis.users[username] = userInfo } header := w.Header() header.Set("X-Auth-Token", userInfo.Token) header.Set("X-Server-Management-Url", lis.managementURL+"/compute") header.Set("X-Storage-Url", lis.managementURL+"/object-store") w.WriteHeader(http.StatusNoContent) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/identityservice/legacy_test.go000066400000000000000000000055531414605405700307130ustar00rootroot00000000000000package identityservice import ( "io/ioutil" "net/http" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/testing/httpsuite" ) type LegacySuite struct { httpsuite.HTTPSuite } var _ = gc.Suite(&LegacySuite{}) func (s *LegacySuite) setupLegacy(user, secret string) (token, managementURL string) { managementURL = s.Server.URL identity := NewLegacy() // Ensure that it conforms to the interface var _ IdentityService = identity identity.SetManagementURL(managementURL) identity.SetupHTTP(s.Mux) if user != "" { userInfo := identity.AddUser(user, secret, "tenant", "default") token = userInfo.Token } return } func LegacyAuthRequest(URL, user, key string) (*http.Response, error) { client := http.DefaultClient request, err := http.NewRequest("GET", URL, nil) if err != nil { return nil, err } if user != "" { request.Header.Set("X-Auth-User", user) } if key != "" { request.Header.Set("X-Auth-Key", key) } return client.Do(request) } func AssertUnauthorized(c *gc.C, response *http.Response) { content, err := ioutil.ReadAll(response.Body) c.Assert(err, gc.IsNil) response.Body.Close() c.Check(response.Header.Get("X-Auth-Token"), gc.Equals, "") c.Check(response.Header.Get("X-Server-Management-Url"), gc.Equals, "") c.Check(string(content), gc.Equals, "") c.Check(response.StatusCode, gc.Equals, http.StatusUnauthorized) } func (s *LegacySuite) TestLegacyFailedAuth(c *gc.C) { s.setupLegacy("", "") // No headers set for Authentication response, err := LegacyAuthRequest(s.Server.URL, "", "") c.Assert(err, gc.IsNil) AssertUnauthorized(c, response) } func (s *LegacySuite) TestLegacyFailedOnlyUser(c *gc.C) { s.setupLegacy("", "") // Missing secret key response, err := LegacyAuthRequest(s.Server.URL, "user", "") c.Assert(err, gc.IsNil) AssertUnauthorized(c, response) } func (s *LegacySuite) TestLegacyNoSuchUser(c *gc.C) { s.setupLegacy("user", "key") // No user matching the username response, err := LegacyAuthRequest(s.Server.URL, "notuser", "key") c.Assert(err, gc.IsNil) AssertUnauthorized(c, response) } func (s *LegacySuite) TestLegacyInvalidAuth(c *gc.C) { s.setupLegacy("user", "secret-key") // Wrong key response, err := LegacyAuthRequest(s.Server.URL, "user", "bad-key") c.Assert(err, gc.IsNil) AssertUnauthorized(c, response) } func (s *LegacySuite) TestLegacyAuth(c *gc.C) { token, serverURL := s.setupLegacy("user", "secret-key") response, err := LegacyAuthRequest(s.Server.URL, "user", "secret-key") c.Assert(err, gc.IsNil) content, err := ioutil.ReadAll(response.Body) response.Body.Close() c.Check(response.Header.Get("X-Auth-Token"), gc.Equals, token) c.Check(response.Header.Get("X-Server-Management-Url"), gc.Equals, serverURL+"/compute") c.Check(response.Header.Get("X-Storage-Url"), gc.Equals, serverURL+"/object-store") c.Check(string(content), gc.Equals, "") c.Check(response.StatusCode, gc.Equals, http.StatusNoContent) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/identityservice/service_test.go000066400000000000000000000012641414605405700311020ustar00rootroot00000000000000package identityservice import ( gc "gopkg.in/check.v1" "gopkg.in/goose.v1/testing/httpsuite" ) // All tests in the IdentityServiceSuite run against each IdentityService // implementation. type IdentityServiceSuite struct { httpsuite.HTTPSuite service IdentityService } var _ = gc.Suite(&IdentityServiceSuite{service: NewUserPass()}) var _ = gc.Suite(&IdentityServiceSuite{service: NewLegacy()}) func (s *IdentityServiceSuite) TestAddUserGivesNewToken(c *gc.C) { userInfo1 := s.service.AddUser("user-1", "password-1", "tenant", "default") userInfo2 := s.service.AddUser("user-2", "password-2", "tenant", "default") c.Assert(userInfo1.Token, gc.Not(gc.Equals), userInfo2.Token) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/identityservice/setup_test.go000066400000000000000000000001651414605405700306010ustar00rootroot00000000000000package identityservice import ( "testing" gc "gopkg.in/check.v1" ) func Test(t *testing.T) { gc.TestingT(t) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/identityservice/userpass.go000066400000000000000000000165041414605405700302530ustar00rootroot00000000000000package identityservice import ( "encoding/json" "fmt" "io/ioutil" "net/http" "gopkg.in/goose.v1/testservices/hook" ) // Implement the v2 User Pass form of identity (Keystone) type ErrorResponse struct { Message string `json:"message"` Code int `json:"code"` Title string `json:"title"` } type ErrorWrapper struct { Error ErrorResponse `json:"error"` } type UserPassRequest struct { Auth struct { PasswordCredentials struct { Username string `json:"username"` Password string `json:"password"` } `json:"passwordCredentials"` TenantName string `json:"tenantName"` } `json:"auth"` } type Endpoint struct { AdminURL string `json:"adminURL"` InternalURL string `json:"internalURL"` PublicURL string `json:"publicURL"` Region string `json:"region"` } type V2Service struct { Name string `json:"name"` Type string `json:"type"` Endpoints []Endpoint } type TokenResponse struct { Expires string `json:"expires"` // should this be a date object? Id string `json:"id"` // Actual token string Tenant struct { Id string `json:"id"` Name string `json:"name"` Description *string `json:"description"` } `json:"tenant"` } type RoleResponse struct { Id string `json:"id"` Name string `json:"name"` TenantId string `json:"tenantId"` } type UserResponse struct { Id string `json:"id"` Name string `json:"name"` Roles []RoleResponse `json:"roles"` } type AccessResponse struct { Access struct { ServiceCatalog []V2Service `json:"serviceCatalog"` Token TokenResponse `json:"token"` User UserResponse `json:"user"` } `json:"access"` } // Taken from: http://docs.openstack.org/api/quick-start/content/index.html#Getting-Credentials-a00665 var exampleResponse = `{ "access": { "serviceCatalog": [ { "endpoints": [ { "adminURL": "https://nova-api.trystack.org:9774/v1.1/1", "internalURL": "https://nova-api.trystack.org:9774/v1.1/1", "publicURL": "https://nova-api.trystack.org:9774/v1.1/1", "region": "RegionOne" } ], "name": "nova", "type": "compute" }, { "endpoints": [ { "adminURL": "https://GLANCE_API_IS_NOT_DISCLOSED/v1.1/1", "internalURL": "https://GLANCE_API_IS_NOT_DISCLOSED/v1.1/1", "publicURL": "https://GLANCE_API_IS_NOT_DISCLOSED/v1.1/1", "region": "RegionOne" } ], "name": "glance", "type": "image" }, { "endpoints": [ { "adminURL": "https://nova-api.trystack.org:5443/v2.0", "internalURL": "https://keystone.trystack.org:5000/v2.0", "publicURL": "https://keystone.trystack.org:5000/v2.0", "region": "RegionOne" } ], "name": "keystone", "type": "identity" } ], "token": { "expires": "2012-02-15T19:32:21", "id": "5df9d45d-d198-4222-9b4c-7a280aa35666", "tenant": { "id": "1", "name": "admin", "description": null } }, "user": { "id": "14", "name": "annegentle", "roles": [ { "id": "2", "name": "Member", "tenantId": "1" } ] } } }` type UserPass struct { hook.TestService Users services []V2Service } func NewUserPass() *UserPass { userpass := &UserPass{ services: make([]V2Service, 0), } userpass.users = make(map[string]UserInfo) userpass.tenants = make(map[string]string) return userpass } func (u *UserPass) RegisterServiceProvider(name, serviceType string, serviceProvider ServiceProvider) { service := V2Service{name, serviceType, serviceProvider.Endpoints()} u.AddService(Service{V2: service}) } func (u *UserPass) AddService(service Service) { u.services = append(u.services, service.V2) } var internalError = []byte(`{ "error": { "message": "Internal failure", "code": 500, "title": Internal Server Error" } }`) func (u *UserPass) ReturnFailure(w http.ResponseWriter, status int, message string) { e := ErrorWrapper{ Error: ErrorResponse{ Message: message, Code: status, Title: http.StatusText(status), }, } if content, err := json.Marshal(e); err != nil { w.Header().Set("Content-Length", fmt.Sprintf("%d", len(internalError))) w.WriteHeader(http.StatusInternalServerError) w.Write(internalError) } else { w.Header().Set("Content-Length", fmt.Sprintf("%d", len(content))) w.WriteHeader(status) w.Write(content) } } // Taken from an actual responses, however it may vary based on actual Openstack implementation const ( notJSON = ("Expecting to find application/json in Content-Type header." + " The server could not comply with the request since it is either malformed" + " or otherwise incorrect. The client is assumed to be in error.") ) func (u *UserPass) ServeHTTP(w http.ResponseWriter, r *http.Request) { var req UserPassRequest // Testing against Canonistack, all responses are application/json, even failures w.Header().Set("Content-Type", "application/json") if r.Header.Get("Content-Type") != "application/json" { u.ReturnFailure(w, http.StatusBadRequest, notJSON) return } if content, err := ioutil.ReadAll(r.Body); err != nil { w.WriteHeader(http.StatusBadRequest) return } else { if err := json.Unmarshal(content, &req); err != nil { u.ReturnFailure(w, http.StatusBadRequest, notJSON) return } } userInfo, errmsg := u.authenticate(req.Auth.PasswordCredentials.Username, req.Auth.PasswordCredentials.Password, "default") if errmsg != "" { u.ReturnFailure(w, http.StatusUnauthorized, errmsg) return } res, err := u.generateAccessResponse(userInfo) if err != nil { u.ReturnFailure(w, http.StatusInternalServerError, err.Error()) return } content, err := json.Marshal(res) if err != nil { u.ReturnFailure(w, http.StatusInternalServerError, err.Error()) return } w.WriteHeader(http.StatusOK) w.Write(content) } func (u *UserPass) generateAccessResponse(userInfo *UserInfo) (*AccessResponse, error) { res := AccessResponse{} // We pre-populate the response with genuine entries so that it looks sane. // XXX: We should really build up valid state for this instead, at the // very least, we should manage the URLs better. if err := json.Unmarshal([]byte(exampleResponse), &res); err != nil { return nil, err } res.Access.ServiceCatalog = u.services res.Access.Token.Id = userInfo.Token res.Access.Token.Tenant.Id = userInfo.TenantId res.Access.User.Id = userInfo.Id if err := u.ProcessControlHook("authorisation", u, &res, userInfo); err != nil { return nil, err } return &res, nil } // setupHTTP attaches all the needed handlers to provide the HTTP API. func (u *UserPass) SetupHTTP(mux *http.ServeMux) { mux.Handle("/tokens", u) } func (u *UserPass) Stop() { // noop } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/identityservice/userpass_test.go000066400000000000000000000102761414605405700313120ustar00rootroot00000000000000package identityservice import ( "encoding/json" "fmt" "io/ioutil" "net/http" "strings" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/testing/httpsuite" ) type UserPassSuite struct { httpsuite.HTTPSuite } var _ = gc.Suite(&UserPassSuite{}) func makeUserPass(user, secret string) (identity *UserPass) { identity = NewUserPass() // Ensure that it conforms to the interface var _ IdentityService = identity if user != "" { identity.AddUser(user, secret, "tenant", "default") } return } func (s *UserPassSuite) setupUserPass(user, secret string) { var identity *UserPass identity = makeUserPass(user, secret) identity.SetupHTTP(s.Mux) return } func (s *UserPassSuite) setupUserPassWithServices(user, secret string, services []Service) { var identity *UserPass identity = makeUserPass(user, secret) for _, service := range services { identity.AddService(service) } identity.SetupHTTP(s.Mux) return } var authTemplate = `{ "auth": { "tenantName": "tenant-something", "passwordCredentials": { "username": "%s", "password": "%s" } } }` func userPassAuthRequest(URL, user, key string) (*http.Response, error) { client := http.DefaultClient body := strings.NewReader(fmt.Sprintf(authTemplate, user, key)) request, err := http.NewRequest("POST", URL+"/tokens", body) request.Header.Set("Content-Type", "application/json") if err != nil { return nil, err } return client.Do(request) } func CheckErrorResponse(c *gc.C, r *http.Response, status int, msg string) { c.Check(r.StatusCode, gc.Equals, status) c.Assert(r.Header.Get("Content-Type"), gc.Equals, "application/json") body, err := ioutil.ReadAll(r.Body) c.Assert(err, gc.IsNil) var errmsg ErrorWrapper err = json.Unmarshal(body, &errmsg) c.Assert(err, gc.IsNil) c.Check(errmsg.Error.Code, gc.Equals, status) c.Check(errmsg.Error.Title, gc.Equals, http.StatusText(status)) if msg != "" { c.Check(errmsg.Error.Message, gc.Equals, msg) } } func (s *UserPassSuite) TestNotJSON(c *gc.C) { // We do everything in userPassAuthRequest, except set the Content-Type s.setupUserPass("user", "secret") client := http.DefaultClient body := strings.NewReader(fmt.Sprintf(authTemplate, "user", "secret")) request, err := http.NewRequest("POST", s.Server.URL+"/tokens", body) c.Assert(err, gc.IsNil) res, err := client.Do(request) c.Assert(err, gc.IsNil) defer res.Body.Close() CheckErrorResponse(c, res, http.StatusBadRequest, notJSON) } func (s *UserPassSuite) TestBadJSON(c *gc.C) { // We do everything in userPassAuthRequest, except set the Content-Type s.setupUserPass("user", "secret") res, err := userPassAuthRequest(s.Server.URL, "garbage\"in", "secret") c.Assert(err, gc.IsNil) defer res.Body.Close() CheckErrorResponse(c, res, http.StatusBadRequest, notJSON) } func (s *UserPassSuite) TestNoSuchUser(c *gc.C) { s.setupUserPass("user", "secret") res, err := userPassAuthRequest(s.Server.URL, "not-user", "secret") c.Assert(err, gc.IsNil) defer res.Body.Close() CheckErrorResponse(c, res, http.StatusUnauthorized, notAuthorized) } func (s *UserPassSuite) TestBadPassword(c *gc.C) { s.setupUserPass("user", "secret") res, err := userPassAuthRequest(s.Server.URL, "user", "not-secret") c.Assert(err, gc.IsNil) defer res.Body.Close() CheckErrorResponse(c, res, http.StatusUnauthorized, invalidUser) } func (s *UserPassSuite) TestValidAuthorization(c *gc.C) { compute_url := "http://testing.invalid/compute" s.setupUserPassWithServices("user", "secret", []Service{ {V2: V2Service{"nova", "compute", []Endpoint{ {PublicURL: compute_url}, }}}}) res, err := userPassAuthRequest(s.Server.URL, "user", "secret") c.Assert(err, gc.IsNil) defer res.Body.Close() c.Check(res.StatusCode, gc.Equals, http.StatusOK) c.Check(res.Header.Get("Content-Type"), gc.Equals, "application/json") content, err := ioutil.ReadAll(res.Body) c.Assert(err, gc.IsNil) var response AccessResponse err = json.Unmarshal(content, &response) c.Assert(err, gc.IsNil) c.Check(response.Access.Token.Id, gc.NotNil) novaURL := "" for _, service := range response.Access.ServiceCatalog { if service.Type == "compute" { novaURL = service.Endpoints[0].PublicURL break } } c.Assert(novaURL, gc.Equals, compute_url) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/identityservice/users.go000066400000000000000000000030251414605405700275410ustar00rootroot00000000000000package identityservice import ( "fmt" "strconv" ) type Users struct { nextUserId int nextTenantId int users map[string]UserInfo tenants map[string]string } func (u *Users) addTenant(tenant string) string { for id, tenantName := range u.tenants { if tenant == tenantName { return id } } u.nextTenantId++ id := strconv.Itoa(u.nextTenantId) u.tenants[id] = tenant return id } func (u *Users) AddUser(user, secret, tenant, authDomain string) *UserInfo { tenantId := u.addTenant(tenant) u.nextUserId++ userInfo := &UserInfo{ secret: secret, Id: strconv.Itoa(u.nextUserId), TenantId: tenantId, authDomain: authDomain, } u.users[user] = *userInfo userInfo, _ = u.authenticate(user, secret, authDomain) return userInfo } func (u *Users) FindUser(token string) (*UserInfo, error) { for _, userInfo := range u.users { if userInfo.Token == token { return &userInfo, nil } } return nil, fmt.Errorf("No user with token %v exists", token) } const ( notAuthorized = "The request you have made requires authentication." invalidUser = "Invalid user / password" ) func (u *Users) authenticate(username, password, domain string) (*UserInfo, string) { userInfo, ok := u.users[username] if !ok { return nil, notAuthorized } if domain != "" && domain != userInfo.authDomain { return nil, invalidUser } if userInfo.secret != password { return nil, invalidUser } if userInfo.Token == "" { userInfo.Token = randomHexToken() u.users[username] = userInfo } return &userInfo, "" } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/identityservice/util.go000066400000000000000000000012051414605405700273530ustar00rootroot00000000000000package identityservice import ( "crypto/rand" "encoding/hex" "fmt" "io" ) type UserInfo struct { Id string TenantId string Token string secret string authDomain string } var randReader = rand.Reader // Generate a bit of random hex data for func randomHexToken() string { raw_bytes := make([]byte, 16) n, err := io.ReadFull(randReader, raw_bytes) if err != nil { panic(fmt.Sprintf( "failed to read 16 random bytes (read %d bytes): %s", n, err.Error())) } hex_bytes := make([]byte, 32) // hex.Encode can't fail, no error checking needed. hex.Encode(hex_bytes, raw_bytes) return string(hex_bytes) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/identityservice/util_test.go000066400000000000000000000054201414605405700304150ustar00rootroot00000000000000package identityservice import ( "bytes" "crypto/rand" "fmt" "io" "testing/iotest" gc "gopkg.in/check.v1" ) type UtilSuite struct{} var _ = gc.Suite(&UtilSuite{}) func (s *UtilSuite) TestRandomHexTokenHasLength(c *gc.C) { val := randomHexToken() c.Assert(val, gc.HasLen, 32) } func (s *UtilSuite) TestRandomHexTokenIsHex(c *gc.C) { val := randomHexToken() for i, b := range val { switch { case (b >= 'a' && b <= 'f') || (b >= '0' && b <= '9'): continue default: c.Logf("char %d of %s was not in the right range", i, val) c.Fail() } } } func (s *UtilSuite) TestDefaultReader(c *gc.C) { raw := make([]byte, 6) c.Assert(string(raw), gc.Equals, "\x00\x00\x00\x00\x00\x00") n, err := io.ReadFull(randReader, raw) c.Assert(err, gc.IsNil) c.Assert(n, gc.Equals, 6) c.Assert(string(raw), gc.Not(gc.Equals), "\x00\x00\x00\x00\x00\x00") } func (s *UtilSuite) TestSetReader(c *gc.C) { orig := randReader // This test will be mutating global state (randReader), ensure that we // restore it sanely even if tests fail defer func() { randReader = orig }() // "randomize" everything to the letter 'n' nRandom := bytes.NewBufferString("nnnnnnnnnnnnnnnnnnnnnnn") c.Assert(randReader, gc.Equals, rand.Reader) cleanup := setReader(nRandom) c.Assert(randReader, gc.Equals, nRandom) raw := make([]byte, 6) n, err := io.ReadFull(randReader, raw) c.Assert(err, gc.IsNil) c.Assert(n, gc.Equals, 6) c.Assert(string(raw), gc.Equals, "nnnnnn") cleanup() c.Assert(randReader, gc.Equals, rand.Reader) } // Change how we get random data, the default is to use crypto/rand // This mostly exists to be able to test error side effects // The return value is a function you can call to restore the previous // randomizer func setReader(r io.Reader) (restore func()) { old := randReader randReader = r return func() { randReader = old } } func (s *UtilSuite) TestNotEnoughRandomBytes(c *gc.C) { // No error, just not enough bytes shortRand := bytes.NewBufferString("xx") cleanup := setReader(shortRand) defer cleanup() c.Assert(randomHexToken, gc.PanicMatches, "failed to read 16 random bytes \\(read 2 bytes\\): unexpected EOF") } type ErrReader struct{} func (e ErrReader) Read(b []byte) (n int, err error) { b[0] = 'x' b[1] = 'x' b[2] = 'x' return 3, fmt.Errorf("Not enough bytes") } func (s *UtilSuite) TestRandomBytesError(c *gc.C) { // No error, just not enough bytes cleanup := setReader(ErrReader{}) defer cleanup() c.Assert(randomHexToken, gc.PanicMatches, "failed to read 16 random bytes \\(read 3 bytes\\): Not enough bytes") } func (s *UtilSuite) TestSlowBytes(c *gc.C) { // Even when we have to read one byte at a time, we can still get our // hex token defer setReader(iotest.OneByteReader(rand.Reader))() val := randomHexToken() c.Assert(val, gc.HasLen, 32) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/identityservice/v3userpass.go000066400000000000000000000146311414605405700305230ustar00rootroot00000000000000package identityservice import ( "encoding/json" "fmt" "io/ioutil" "net/http" "time" "gopkg.in/goose.v1/testservices/hook" ) // V3UserPassRequest Implement the v3 User Pass form of identity (Keystone) type V3UserPassRequest struct { Auth struct { Identity struct { Methods []string `json:"methods"` Password struct { User struct { Name string `json:"name"` Password string `json:"password"` Domain struct { Name string `json:"name,omitempty"` } `json:"domain"` } `json:"user"` } `json:"password"` } `json:"identity"` Scope struct { Project struct { Name string `json:"name"` Domain struct { Name string `json:"name,omitempty"` } `json:"domain,omitempty"` } `json:"project"` Domain struct { Name string `json:"name,omitempty"` } `json:"domain"` } `json:"scope"` } `json:"auth"` } // V3Endpoint represents endpoints to a Service type V3Endpoint struct { Interface string `json:"interface"` RegionID string `json:"region_id"` URL string `json:"url"` } // NewV3Endpoints returns an array of V3Endpoint for the given Region and the // passed admin, internal and public URLs. func NewV3Endpoints(adminURL, internalURL, publicURL, regionID string) []V3Endpoint { var eps []V3Endpoint if adminURL != "" { eps = append(eps, V3Endpoint{ RegionID: regionID, Interface: "admin", URL: adminURL, }) } if internalURL != "" { eps = append(eps, V3Endpoint{ RegionID: regionID, Interface: "internal", URL: internalURL, }) } if publicURL != "" { eps = append(eps, V3Endpoint{ RegionID: regionID, Interface: "public", URL: publicURL, }) } return eps } // V3Service represents an OpenStack web service that you can access through a URL. type V3Service struct { ID string `json:"id"` Name string `json:"name"` Type string `json:"type"` Endpoints []V3Endpoint `json:"endpoints"` } // V3TokenResponse repesent a Token returned as a response to authentication to // keystone v3. type V3TokenResponse struct { Expires time.Time `json:"expires_at"` Issued time.Time `json:"issued_at"` Methods []string `json:"methods"` Catalog []V3Service `json:"catalog,omitempty"` Project *V3Project `json:"project,omitempty"` Domain *V3Domain `json:"domain,omitempty"` User struct { ID string `json:"id"` Name string `json:"name"` } `json:"user"` } // V3Project represent an openstack project, A project is the base unit of ownership. // Resources are owned by a specific project. A project is owned by a specific domain. type V3Project struct { ID string `json:"id,omitempty"` } // V3Domain represents an authentication domain. type V3Domain struct { ID string `json:"id,omitempty"` Name string `json:"name,omitempty"` } // V3UserPass represents an authenticated user to a service. type V3UserPass struct { hook.TestService Users services []V3Service } // NewV3UserPass returns a new V3UserPass func NewV3UserPass() *V3UserPass { userpass := &V3UserPass{ services: make([]V3Service, 0), } userpass.users = make(map[string]UserInfo) userpass.tenants = make(map[string]string) return userpass } // RegisterServiceProvider registers V3UserPass as a service provider. func (u *V3UserPass) RegisterServiceProvider(name, serviceType string, serviceProvider ServiceProvider) { service := V3Service{ ID: name, Name: name, Type: serviceType, Endpoints: serviceProvider.V3Endpoints(), } u.AddService(Service{V3: service}) } // AddService adds a service to the current V3UserPass. func (u *V3UserPass) AddService(service Service) { u.services = append(u.services, service.V3) } // ReturnFailure wraps and returns an error through the http connection. func (u *V3UserPass) ReturnFailure(w http.ResponseWriter, status int, message string) { e := ErrorWrapper{ Error: ErrorResponse{ Message: message, Code: status, Title: http.StatusText(status), }, } if content, err := json.Marshal(e); err != nil { w.Header().Set("Content-Length", fmt.Sprintf("%d", len(internalError))) w.WriteHeader(http.StatusInternalServerError) w.Write(internalError) } else { w.Header().Set("Content-Length", fmt.Sprintf("%d", len(content))) w.WriteHeader(status) w.Write(content) } } // ServeHTTP serves V3UserPass for testing purposes. func (u *V3UserPass) ServeHTTP(w http.ResponseWriter, r *http.Request) { var req V3UserPassRequest // Testing against Canonistack, all responses are application/json, even failures w.Header().Set("Content-Type", "application/json") if r.Header.Get("Content-Type") != "application/json" { u.ReturnFailure(w, http.StatusBadRequest, notJSON) return } if content, err := ioutil.ReadAll(r.Body); err != nil { w.WriteHeader(http.StatusBadRequest) return } else { if err := json.Unmarshal(content, &req); err != nil { u.ReturnFailure(w, http.StatusBadRequest, notJSON) return } } domain := req.Auth.Scope.Project.Domain.Name if domain == "" { domain = req.Auth.Scope.Domain.Name } if domain == "" { domain = "default" } userInfo, errmsg := u.authenticate( req.Auth.Identity.Password.User.Name, req.Auth.Identity.Password.User.Password, domain, ) if errmsg != "" { u.ReturnFailure(w, http.StatusUnauthorized, errmsg) return } res, err := u.generateV3TokenResponse(userInfo) if err != nil { u.ReturnFailure(w, http.StatusInternalServerError, err.Error()) return } if req.Auth.Scope.Project.Name != "" { res.Project = &V3Project{ ID: u.addTenant(req.Auth.Scope.Project.Name), } } if req.Auth.Scope.Domain.Name != "" { res.Domain = &V3Domain{ Name: req.Auth.Scope.Domain.Name, } } content, err := json.Marshal(struct { Token *V3TokenResponse `json:"token"` }{ Token: res, }) if err != nil { u.ReturnFailure(w, http.StatusInternalServerError, err.Error()) return } w.Header().Set("X-Subject-Token", userInfo.Token) w.WriteHeader(http.StatusCreated) w.Write(content) } func (u *V3UserPass) generateV3TokenResponse(userInfo *UserInfo) (*V3TokenResponse, error) { res := V3TokenResponse{} res.Issued = time.Now() res.Expires = res.Issued.Add(24 * time.Hour) res.Methods = []string{"password"} res.Catalog = u.services res.User.ID = userInfo.Id return &res, nil } // SetupHTTP attaches all the needed handlers to provide the HTTP API. func (u *V3UserPass) SetupHTTP(mux *http.ServeMux) { mux.Handle("/v3/auth/tokens", u) } func (u *V3UserPass) Stop() { // noop } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/identityservice/v3userpass_test.go000066400000000000000000000100301414605405700315470ustar00rootroot00000000000000package identityservice import ( "encoding/json" "fmt" "io/ioutil" "net/http" "strings" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/testing/httpsuite" ) type V3UserPassSuite struct { httpsuite.HTTPSuite } var _ = gc.Suite(&V3UserPassSuite{}) func makeV3UserPass(user, secret string) (identity *V3UserPass) { identity = NewV3UserPass() // Ensure that it conforms to the interface var _ IdentityService = identity if user != "" { identity.AddUser(user, secret, "tenant", "default") } return } func (s *V3UserPassSuite) setupUserPass(user, secret string) { var identity *V3UserPass identity = makeV3UserPass(user, secret) identity.SetupHTTP(s.Mux) return } func (s *V3UserPassSuite) setupUserPassWithServices(user, secret string, services []Service) { var identity *V3UserPass identity = makeV3UserPass(user, secret) for _, service := range services { identity.AddService(service) } identity.SetupHTTP(s.Mux) return } var v3AuthTemplate = `{ "auth": { "identity": { "method":["password"], "password": { "user": { "domain": { "id": "default" }, "name": "%s", "password": "%s" } } } } }` func v3UserPassAuthRequest(URL, user, key string) (*http.Response, error) { client := http.DefaultClient body := strings.NewReader(fmt.Sprintf(v3AuthTemplate, user, key)) request, err := http.NewRequest("POST", URL+"/v3/auth/tokens", body) request.Header.Set("Content-Type", "application/json") if err != nil { return nil, err } return client.Do(request) } func (s *V3UserPassSuite) TestNotJSON(c *gc.C) { // We do everything in userPassAuthRequest, except set the Content-Type s.setupUserPass("user", "secret") client := http.DefaultClient body := strings.NewReader(fmt.Sprintf(authTemplate, "user", "secret")) request, err := http.NewRequest("POST", s.Server.URL+"/v3/auth/tokens", body) c.Assert(err, gc.IsNil) res, err := client.Do(request) c.Assert(err, gc.IsNil) defer res.Body.Close() CheckErrorResponse(c, res, http.StatusBadRequest, notJSON) } func (s *V3UserPassSuite) TestBadJSON(c *gc.C) { // We do everything in userPassAuthRequest, except set the Content-Type s.setupUserPass("user", "secret") res, err := v3UserPassAuthRequest(s.Server.URL, "garbage\"in", "secret") c.Assert(err, gc.IsNil) defer res.Body.Close() CheckErrorResponse(c, res, http.StatusBadRequest, notJSON) } func (s *V3UserPassSuite) TestNoSuchUser(c *gc.C) { s.setupUserPass("user", "secret") res, err := v3UserPassAuthRequest(s.Server.URL, "not-user", "secret") c.Assert(err, gc.IsNil) defer res.Body.Close() CheckErrorResponse(c, res, http.StatusUnauthorized, notAuthorized) } func (s *V3UserPassSuite) TestBadPassword(c *gc.C) { s.setupUserPass("user", "secret") res, err := v3UserPassAuthRequest(s.Server.URL, "user", "not-secret") c.Assert(err, gc.IsNil) defer res.Body.Close() CheckErrorResponse(c, res, http.StatusUnauthorized, invalidUser) } func (s *V3UserPassSuite) TestValidAuthorization(c *gc.C) { compute_url := "http://testing.invalid/compute" s.setupUserPassWithServices("user", "secret", []Service{ {V3: V3Service{Name: "nova", Type: "compute", Endpoints: NewV3Endpoints("", "", compute_url, "")}}}) res, err := v3UserPassAuthRequest(s.Server.URL, "user", "secret") c.Assert(err, gc.IsNil) defer res.Body.Close() c.Check(res.StatusCode, gc.Equals, http.StatusCreated) c.Check(res.Header.Get("Content-Type"), gc.Equals, "application/json") content, err := ioutil.ReadAll(res.Body) c.Assert(err, gc.IsNil) var response struct { Token V3TokenResponse `json:"token"` } err = json.Unmarshal(content, &response) c.Assert(err, gc.IsNil) c.Check(res.Header.Get("X-Subject-Token"), gc.Not(gc.Equals), "") novaURL := "" for _, service := range response.Token.Catalog { if service.Type == "compute" { for _, ep := range service.Endpoints { if ep.Interface == "public" { novaURL = ep.URL break } } break } } c.Assert(novaURL, gc.Equals, compute_url) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/neutronmodel/000077500000000000000000000000001414605405700253525ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/neutronmodel/neutronmodel.go000066400000000000000000000445651414605405700304320ustar00rootroot00000000000000// neutronmodel is a package to allow novatestservices and neutrontestservices // to share data related to FloatingIPs, Networks and SecurityGroups. package neutronmodel import ( "net" "strconv" "sync" "gopkg.in/goose.v1/neutron" "gopkg.in/goose.v1/nova" "gopkg.in/goose.v1/testservices" ) type NeutronModel struct { groups map[string]neutron.SecurityGroupV2 rules map[string]neutron.SecurityGroupRuleV2 floatingIPs map[string]neutron.FloatingIPV2 networks map[string]neutron.NetworkV2 serverGroups map[string][]string serverIPs map[string][]string nextGroupId int nextRuleId int nextIPId int rwMu *sync.RWMutex } // New setups the default data Network and Security Group for the neutron and // nova test services. func New() *NeutronModel { // Real openstack instances have a default security group "out of the box". So we add it here. defaultSecurityGroups := []neutron.SecurityGroupV2{ {Id: "999", TenantId: "1", Name: "default", Description: "default group"}, } // There are no create/delete network/subnet commands, so make a few default defaultNetworks := []neutron.NetworkV2{ { // for use by opentstack provider test Id: "1", Name: "net", SubnetIds: []string{"sub-net"}, External: false, }, { Id: "999", Name: "private_999", SubnetIds: []string{"999-01"}, External: false, }, { Id: "998", Name: "ext-net", SubnetIds: []string{"998-01"}, External: true, AvailabilityZones: []string{"test-available"}, }, { Id: "997", Name: "ext-net-wrong-az", SubnetIds: []string{"997-01"}, External: true, AvailabilityZones: []string{"unavailable-az"}, }, } neutronModel := &NeutronModel{ groups: make(map[string]neutron.SecurityGroupV2), rules: make(map[string]neutron.SecurityGroupRuleV2), floatingIPs: make(map[string]neutron.FloatingIPV2), networks: make(map[string]neutron.NetworkV2), rwMu: &sync.RWMutex{}, } for _, group := range defaultSecurityGroups { err := neutronModel.AddSecurityGroup(group) if err != nil { panic(err) } } for _, net := range defaultNetworks { err := neutronModel.AddNetwork(net) if err != nil { panic(err) } } return neutronModel } // convertNeutronToNovaSecurityGroup converts a nova.SecurityGroup to // neutron.SecurityGroupV2. func (n *NeutronModel) convertNeutronToNovaSecurityGroup(group neutron.SecurityGroupV2) nova.SecurityGroup { novaGroup := nova.SecurityGroup{ TenantId: group.TenantId, Id: group.Id, Name: group.Name, Description: group.Description, Rules: []nova.SecurityGroupRule{}, } var novaRules []nova.SecurityGroupRule for _, rule := range group.Rules { novaRules = append(novaRules, nova.SecurityGroupRule{ FromPort: rule.PortRangeMin, ToPort: rule.PortRangeMax, Id: rule.Id, ParentGroupId: rule.ParentGroupId, IPProtocol: rule.IPProtocol, IPRange: map[string]string{"cidr": rule.RemoteIPPrefix}, }) } if len(novaRules) > 0 { novaGroup.Rules = novaRules } return novaGroup } // convertNeutronToNovaSecurityGroup converts a neutron.SecurityGroupV2 to a // nova.SecurityGroup. func (n *NeutronModel) convertNovaToNeutronSecurityGroup(group nova.SecurityGroup) neutron.SecurityGroupV2 { neutronGroup := neutron.SecurityGroupV2{ TenantId: group.TenantId, Id: group.Id, Name: group.Name, Description: group.Description, Rules: []neutron.SecurityGroupRuleV2{}, } var neutronRules []neutron.SecurityGroupRuleV2 for _, rule := range group.Rules { neutronRules = append(neutronRules, neutron.SecurityGroupRuleV2{ PortRangeMin: rule.FromPort, PortRangeMax: rule.ToPort, Id: rule.Id, ParentGroupId: rule.ParentGroupId, IPProtocol: rule.IPProtocol, RemoteIPPrefix: rule.IPRange["cidr"], }) } if len(neutronRules) > 0 { neutronGroup.Rules = neutronRules } return neutronGroup } // UpdateSecurityGroup updates an existing security group given a // neutron.SecurityGroupRuleV2. func (n *NeutronModel) UpdateSecurityGroup(group neutron.SecurityGroupV2) error { n.rwMu.Lock() defer n.rwMu.Unlock() existingGroup, err := n.SecurityGroup(group.Id) if err != nil { return testservices.NewSecurityGroupByIDNotFoundError(group.Id) } existingGroup.Name = group.Name existingGroup.Description = group.Description n.groups[group.Id] = *existingGroup return nil } // UpdateNovaSecurityGroup updates an existing security group given a nova.SecurityGroup. func (n *NeutronModel) UpdateNovaSecurityGroup(group nova.SecurityGroup) error { return n.UpdateSecurityGroup(n.convertNovaToNeutronSecurityGroup(group)) } // AddSecurityGroup creates a new security group given a neutron.SecurityGroupV2. func (n *NeutronModel) AddSecurityGroup(group neutron.SecurityGroupV2) error { n.rwMu.Lock() defer n.rwMu.Unlock() if _, err := n.SecurityGroup(group.Id); err == nil { return testservices.NewSecurityGroupAlreadyExistsError(group.Id) } // Neutron adds 2 default egress security group rules to new security // groups, copy that behavior here. id, _ := strconv.Atoi(group.Id) id1 := id * 999 id2 := id * 998 group.Rules = []neutron.SecurityGroupRuleV2{ { Direction: "egress", EthernetType: "IPv4", Id: strconv.Itoa(id1), TenantId: group.TenantId, ParentGroupId: group.Id, }, { Direction: "egress", EthernetType: "IPv6", Id: strconv.Itoa(id2), TenantId: group.TenantId, ParentGroupId: group.Id, }, } for _, rule := range group.Rules { n.rules[rule.Id] = rule } n.groups[group.Id] = group return nil } // AddNovaSecurityGroup creates a new security group given a nova.SecurityGroup. func (n *NeutronModel) AddNovaSecurityGroup(group nova.SecurityGroup) error { err := n.AddSecurityGroup(n.convertNovaToNeutronSecurityGroup(group)) if err != nil { return err } return err } // SecurityGroup retrieves an existing group by ID, data in neutron.SecurityGroupV2 form. func (n *NeutronModel) SecurityGroup(groupId string) (*neutron.SecurityGroupV2, error) { if n.rwMu == nil { n.rwMu.RLock() defer n.rwMu.RUnlock() } group, ok := n.groups[groupId] if !ok { return nil, testservices.NewSecurityGroupByIDNotFoundError(groupId) } return &group, nil } // NovaSecurityGroup retrieves an existing group by ID, data in nova.SecurityGroup form. func (n *NeutronModel) NovaSecurityGroup(groupId string) (*nova.SecurityGroup, error) { group, err := n.SecurityGroup(groupId) if err != nil { return nil, err } novaGroup := n.convertNeutronToNovaSecurityGroup(*group) return &novaGroup, nil } // SecurityGroupByName retrieves an existing named group, data in // neutron.SecurityGroupV2 form. func (n *NeutronModel) SecurityGroupByName(groupName string) ([]neutron.SecurityGroupV2, error) { n.rwMu.RLock() defer n.rwMu.RUnlock() var foundGrps []neutron.SecurityGroupV2 for _, group := range n.groups { if group.Name == groupName { foundGrps = append(foundGrps, group) } } return foundGrps, nil //return nil, testservices.NewSecurityGroupByNameNotFoundError(groupName) } // NovaSecurityGroupByName retrieves an existing named group, data in // nova.SecurityGroup form. func (n *NeutronModel) NovaSecurityGroupByName(groupName string) (*nova.SecurityGroup, error) { groups, err := n.SecurityGroupByName(groupName) if err != nil { return nil, err } // Nova SecurityGroupsByName expects only 1 return value, return // the first one found. novaGroup := n.convertNeutronToNovaSecurityGroup(groups[0]) return &novaGroup, nil } // AllSecurityGroups returns a list of all existing groups, data in // neutron.SecurityGroupV2 form. func (n *NeutronModel) AllSecurityGroups() []neutron.SecurityGroupV2 { n.rwMu.RLock() defer n.rwMu.RUnlock() var groups []neutron.SecurityGroupV2 for _, group := range n.groups { groups = append(groups, group) } return groups } // AllNovaSecurityGroups returns a list of all existing groups, // nova.SecurityGroup form. func (n *NeutronModel) AllNovaSecurityGroups() []nova.SecurityGroup { neutronGroups := n.AllSecurityGroups() var groups []nova.SecurityGroup for _, group := range neutronGroups { groups = append(groups, n.convertNeutronToNovaSecurityGroup(group)) } return groups } // RemoveSecurityGroup deletes an existing group. func (n *NeutronModel) RemoveSecurityGroup(groupId string) error { n.rwMu.Lock() defer n.rwMu.Unlock() if _, err := n.SecurityGroup(groupId); err != nil { return err } delete(n.groups, groupId) return nil } // AddSecurityGroupRule creates a new rule in an existing group. // This can be either an ingress or an egress rule (see the notes // about neutron.RuleInfoV2). func (n *NeutronModel) AddSecurityGroupRule(ruleId string, rule neutron.RuleInfoV2) error { n.rwMu.Lock() defer n.rwMu.Unlock() if _, err := n.SecurityGroupRule(ruleId); err == nil { return testservices.NewNeutronSecurityGroupRuleAlreadyExistsError(rule.ParentGroupId) } group, err := n.SecurityGroup(rule.ParentGroupId) if err != nil { return err } newrule := neutron.SecurityGroupRuleV2{ ParentGroupId: rule.ParentGroupId, Id: ruleId, RemoteIPPrefix: rule.RemoteIPPrefix, } if rule.Direction == "ingress" || rule.Direction == "egress" { newrule.Direction = rule.Direction } else { return testservices.NewInvalidDirectionSecurityGroupError(rule.Direction) } if rule.PortRangeMin != 0 { newrule.PortRangeMin = &rule.PortRangeMin } if rule.PortRangeMax != 0 { newrule.PortRangeMax = &rule.PortRangeMax } if rule.IPProtocol != "" { newrule.IPProtocol = &rule.IPProtocol } switch rule.EthernetType { case "": // Neutron assumes IPv4 if no EthernetType is specified newrule.EthernetType = "IPv4" case "IPv4", "IPv6": newrule.EthernetType = rule.EthernetType default: return testservices.NewSecurityGroupRuleInvalidEthernetType(rule.EthernetType) } if newrule.RemoteIPPrefix != "" { ip, _, err := net.ParseCIDR(newrule.RemoteIPPrefix) if err != nil { return testservices.NewSecurityGroupRuleInvalidCIDR(rule.RemoteIPPrefix) } if (newrule.EthernetType == "IPv4" && ip.To4() == nil) || (newrule.EthernetType == "IPv6" && ip.To4() != nil) { return testservices.NewSecurityGroupRuleParameterConflict("ethertype", newrule.EthernetType, "CIDR", newrule.RemoteIPPrefix) } } if group.TenantId != "" { newrule.TenantId = group.TenantId } group.Rules = append(group.Rules, newrule) n.groups[group.Id] = *group n.rules[newrule.Id] = newrule return nil } // AddSecurityGroupRule creates a new rule in an existing group, data in nova.RuleInfo // form. Rule is assumed to be ingress. func (n *NeutronModel) AddNovaSecurityGroupRule(ruleId string, rule nova.RuleInfo) error { neutronRule := neutron.RuleInfoV2{ IPProtocol: rule.IPProtocol, PortRangeMin: rule.FromPort, PortRangeMax: rule.ToPort, RemoteIPPrefix: rule.Cidr, ParentGroupId: rule.ParentGroupId, Direction: "ingress", } return n.AddSecurityGroupRule(ruleId, neutronRule) } // HasSecurityGroupRule returns whether the given group contains the given rule, // or (when groupId="-1") whether the given rule exists. func (n *NeutronModel) HasSecurityGroupRule(groupId, ruleId string) bool { n.rwMu.RLock() defer n.rwMu.RUnlock() rule, ok := n.rules[ruleId] _, err := n.SecurityGroup(groupId) return ok && (groupId == "-1" || (err == nil && rule.ParentGroupId == groupId)) } // SecurityGroupRule retrieves an existing rule by ID, data in neutron.SecurityGroupRuleV2 form. func (n *NeutronModel) SecurityGroupRule(ruleId string) (*neutron.SecurityGroupRuleV2, error) { if n.rwMu == nil { n.rwMu.RLock() defer n.rwMu.RUnlock() } rule, ok := n.rules[ruleId] if !ok { return nil, testservices.NewSecurityGroupRuleNotFoundError(ruleId) } return &rule, nil } // SecurityGroupRule retrieves an existing rule by ID, data in nova.SecurityGroupRule form. func (n *NeutronModel) NovaSecurityGroupRule(ruleId string) (*nova.SecurityGroupRule, error) { rule, err := n.SecurityGroupRule(ruleId) if err != nil { return nil, err } novaRule := &nova.SecurityGroupRule{ IPProtocol: rule.IPProtocol, FromPort: rule.PortRangeMin, ToPort: rule.PortRangeMax, ParentGroupId: rule.ParentGroupId, } return novaRule, nil } // RemoveSecurityGroupRule deletes an existing rule from its group. func (n *NeutronModel) RemoveSecurityGroupRule(ruleId string) error { n.rwMu.Lock() defer n.rwMu.Unlock() rule, err := n.SecurityGroupRule(ruleId) if err != nil { return err } if group, err := n.SecurityGroup(rule.ParentGroupId); err == nil { idx := -1 for ri, ru := range group.Rules { if ru.Id == ruleId { idx = ri break } } if idx != -1 { group.Rules = append(group.Rules[:idx], group.Rules[idx+1:]...) n.groups[group.Id] = *group } // Silently ignore missing rules... } // ...or groups delete(n.rules, ruleId) return nil } // AddFloatingIP creates a new floating IP address in the pool, given a neutron.FloatingIPV2. func (n *NeutronModel) AddFloatingIP(ip neutron.FloatingIPV2) error { n.rwMu.Lock() defer n.rwMu.Unlock() if _, err := n.FloatingIP(ip.Id); err == nil { return testservices.NewFloatingIPExistsError(ip.Id) } n.floatingIPs[ip.Id] = ip return nil } // AddNovaFloatingIP creates a new floatingIP IP address in the pool, given a nova.FloatingIP. func (n *NeutronModel) AddNovaFloatingIP(ip nova.FloatingIP) error { fip := neutron.FloatingIPV2{Id: ip.Id, IP: ip.IP} if ip.FixedIP != nil { fip.FixedIP = *ip.FixedIP } return n.AddFloatingIP(fip) } // HasFloatingIP returns whether the given floating IP address exists. func (n *NeutronModel) HasFloatingIP(address string) bool { n.rwMu.RLock() defer n.rwMu.RUnlock() if len(n.floatingIPs) == 0 { return false } for _, fip := range n.floatingIPs { if fip.IP == address { return true } } return false } // FloatingIP retrieves a neutron floating IP by ID. func (n *NeutronModel) FloatingIP(ipId string) (*neutron.FloatingIPV2, error) { if n.rwMu == nil { n.rwMu.RLock() defer n.rwMu.RUnlock() } ip, ok := n.floatingIPs[ipId] if !ok { return nil, testservices.NewFloatingIPNotFoundError(ipId) } return &ip, nil } // NovaFloatingIP retrieves a nova floating IP by ID. func (n *NeutronModel) NovaFloatingIP(ipId string) (*nova.FloatingIP, error) { fip, err := n.FloatingIP(ipId) if err != nil { return nil, err } return &nova.FloatingIP{Id: fip.Id, IP: fip.IP, FixedIP: &fip.FixedIP}, nil } // FloatingIPByAddr retrieves a neutron floating IP by address. func (n *NeutronModel) FloatingIPByAddr(address string) (*neutron.FloatingIPV2, error) { n.rwMu.RLock() defer n.rwMu.RUnlock() for _, fip := range n.floatingIPs { if fip.IP == address { return &fip, nil } } return nil, testservices.NewFloatingIPNotFoundError(address) } // NovaFloatingIPByAddr retrieves a nova floating IP by address. func (n *NeutronModel) NovaFloatingIPByAddr(address string) (*nova.FloatingIP, error) { fip, err := n.FloatingIPByAddr(address) if err != nil { return nil, err } return &nova.FloatingIP{Id: fip.Id, IP: fip.IP, FixedIP: &fip.FixedIP}, nil } // AllFloatingIPs returns a list of all created floating IPs, data in // neutron.FloatingIPV2 form. func (n *NeutronModel) AllFloatingIPs() []neutron.FloatingIPV2 { n.rwMu.RLock() defer n.rwMu.RUnlock() var fips []neutron.FloatingIPV2 for _, fip := range n.floatingIPs { fips = append(fips, fip) } return fips } // AllNovaFloatingIPs returns a list of all created floating IPs, data in // nova.FloatingIP form. func (n *NeutronModel) AllNovaFloatingIPs() []nova.FloatingIP { neutronFips := n.AllFloatingIPs() var novaFips []nova.FloatingIP for _, fip := range neutronFips { novaFips = append(novaFips, nova.FloatingIP{ Id: fip.Id, IP: fip.IP, FixedIP: &fip.FixedIP, }) } return novaFips } // RemoveFloatingIP deletes an existing floating IP by ID. func (n *NeutronModel) RemoveFloatingIP(ipId string) error { n.rwMu.Lock() defer n.rwMu.Unlock() if _, err := n.FloatingIP(ipId); err != nil { return err } delete(n.floatingIPs, ipId) return nil } // UpdateNovaFloatingIP updates the Fixed IP, given a nova.FloatingIP. func (n *NeutronModel) UpdateNovaFloatingIP(fip *nova.FloatingIP) error { n.rwMu.Lock() defer n.rwMu.Unlock() ip, ok := n.floatingIPs[fip.Id] if !ok { return testservices.NewFloatingIPNotFoundError(fip.Id) } if fip.FixedIP != nil { ip.FixedIP = *fip.FixedIP } n.floatingIPs[fip.Id] = ip return nil } // AllNetworks returns a list of all existing networks in neutron.NetworkV2. func (n *NeutronModel) AllNetworks() (networks []neutron.NetworkV2) { n.rwMu.RLock() defer n.rwMu.RUnlock() for _, net := range n.networks { networks = append(networks, net) } return networks } // AllNovaNetworks returns of list of all existing networks in nova.Network form. func (n *NeutronModel) AllNovaNetworks() (networks []nova.Network) { neutronNetworks := n.AllNetworks() var novaNetworks []nova.Network for _, net := range neutronNetworks { // copy Id and Name to new Nova Network, leave off Cidr for // Neutron networking keeps Cidr in a subnet, not a network. novaNetworks = append(novaNetworks, nova.Network{ Id: net.Id, Label: net.Name, }) } return novaNetworks } // Network retrieves the network in Neutron Network form by ID. func (n *NeutronModel) Network(networkId string) (*neutron.NetworkV2, error) { if n.rwMu == nil { n.rwMu.RLock() defer n.rwMu.RUnlock() } network, ok := n.networks[networkId] if !ok { return nil, testservices.NewNetworkNotFoundError(networkId) } return &network, nil } // NovaNetwork retrieves the network in Nova Network form by ID. func (n *NeutronModel) NovaNetwork(networkId string) (*nova.Network, error) { neutronNet, err := n.Network(networkId) if err != nil { return nil, err } return &nova.Network{Id: neutronNet.Id, Label: neutronNet.Name}, nil } // AddNetwork creates a new network. func (n *NeutronModel) AddNetwork(network neutron.NetworkV2) error { n.rwMu.Lock() defer n.rwMu.Unlock() if _, err := n.Network(network.Id); err == nil { return testservices.NewNetworkAlreadyExistsError(network.Id) } if network.SubnetIds == nil { network.SubnetIds = []string{} } n.networks[network.Id] = network return nil } // RemoveNetwork deletes an existing group. func (n *NeutronModel) RemoveNetwork(netId string) error { n.rwMu.Lock() defer n.rwMu.Unlock() if _, err := n.Network(netId); err != nil { return err } delete(n.networks, netId) return nil } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/neutronservice/000077500000000000000000000000001414605405700257125ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/neutronservice/service.go000066400000000000000000000264501414605405700277100ustar00rootroot00000000000000// Neutron double testing service - internal direct API implementation package neutronservice import ( "net/url" "regexp" "strconv" "strings" "gopkg.in/goose.v1/neutron" "gopkg.in/goose.v1/testservices" "gopkg.in/goose.v1/testservices/identityservice" "gopkg.in/goose.v1/testservices/neutronmodel" ) var _ testservices.HttpService = (*Neutron)(nil) var _ identityservice.ServiceProvider = (*Neutron)(nil) // Neutron implements a OpenStack Neutron testing service and // contains the service double's internal state. type Neutron struct { testservices.ServiceInstance neutronModel *neutronmodel.NeutronModel groups map[string]neutron.SecurityGroupV2 rules map[string]neutron.SecurityGroupRuleV2 floatingIPs map[string]neutron.FloatingIPV2 networks map[string]neutron.NetworkV2 subnets map[string]neutron.SubnetV2 nextGroupId int nextRuleId int nextIPId int } func errorJSONEncode(err error) (int, string) { serverError, ok := err.(*testservices.ServerError) if !ok { serverError = testservices.NewInternalServerError(err.Error()) } return serverError.Code(), serverError.AsJSON() } // endpoint returns endpoint URL from the given path. // openstack catalog list // | neutron | network | RegionOne // | | | publicURL: http://:9696 // | | | internalURL: http://:9696 // | | | adminURL: http://:9696 func (n *Neutron) endpointURL(version bool, path string) string { ep := n.Scheme + "://" + n.Hostname if version { ep += n.VersionPath + "/" } if path != "" { ep += strings.TrimLeft(path, "/") } return ep } func (n *Neutron) Endpoints() []identityservice.Endpoint { ep := identityservice.Endpoint{ AdminURL: n.endpointURL(false, ""), InternalURL: n.endpointURL(false, ""), PublicURL: n.endpointURL(false, ""), Region: n.Region, } return []identityservice.Endpoint{ep} } func (n *Neutron) V3Endpoints() []identityservice.V3Endpoint { url := n.endpointURL(false, "") return identityservice.NewV3Endpoints(url, url, url, n.RegionID) } // New creates an instance of the Neutron object, given the parameters. func New(hostURL, versionPath, tenantId, region string, identityService, fallbackIdentity identityservice.IdentityService) *Neutron { URL, err := url.Parse(hostURL) if err != nil { panic(err) } hostname := URL.Host if !strings.HasSuffix(hostname, "/") { hostname += "/" } defaultSubnets := []neutron.SubnetV2{ { Id: "999-01", NetworkId: "999", Name: "subnet-999", Cidr: "10.9.0.0/24", }, { Id: "998-01", NetworkId: "998", Name: "subnet-998", Cidr: "10.8.0.0/24", }, { Id: "997-01", NetworkId: "997", Name: "subnet-997", Cidr: "2001:db8::/32", }, } neutronService := &Neutron{ subnets: make(map[string]neutron.SubnetV2), ServiceInstance: testservices.ServiceInstance{ IdentityService: identityService, FallbackIdentityService: fallbackIdentity, Scheme: URL.Scheme, Hostname: hostname, VersionPath: versionPath, TenantId: tenantId, Region: region, }, } if identityService != nil { identityService.RegisterServiceProvider("neutron", "network", neutronService) } for _, subnet := range defaultSubnets { err := neutronService.addSubnet(subnet) if err != nil { panic(err) } } return neutronService } func (n *Neutron) Stop() { // noop } // AddNeutronModel setups up the test double for shared data between the nova // and neutron test doubles. Required for the neutron test double. func (n *Neutron) AddNeutronModel(neutronModel *neutronmodel.NeutronModel) { n.neutronModel = neutronModel } // updateSecurityGroup updates an existing security group. func (n *Neutron) updateSecurityGroup(group neutron.SecurityGroupV2) error { if err := n.ProcessFunctionHook(n, group); err != nil { return err } return n.neutronModel.UpdateSecurityGroup(group) } // addSecurityGroup creates a new security group. func (n *Neutron) addSecurityGroup(group neutron.SecurityGroupV2) error { if err := n.ProcessFunctionHook(n, group); err != nil { return err } return n.neutronModel.AddSecurityGroup(group) } // securityGroup retrieves an existing group by ID. func (n *Neutron) securityGroup(groupId string) (*neutron.SecurityGroupV2, error) { if err := n.ProcessFunctionHook(n, groupId); err != nil { return nil, err } return n.neutronModel.SecurityGroup(groupId) } // securityGroupByName retrieves an existing named group. func (n *Neutron) securityGroupByName(groupName string) ([]neutron.SecurityGroupV2, error) { if err := n.ProcessFunctionHook(n, groupName); err != nil { return nil, err } return n.neutronModel.SecurityGroupByName(groupName) } // allSecurityGroups returns a list of all existing groups. func (n *Neutron) allSecurityGroups() []neutron.SecurityGroupV2 { return n.neutronModel.AllSecurityGroups() } // removeSecurityGroup deletes an existing group. func (n *Neutron) removeSecurityGroup(groupId string) error { if err := n.ProcessFunctionHook(n, groupId); err != nil { return err } return n.neutronModel.RemoveSecurityGroup(groupId) } // addSecurityGroupRule creates a new rule in an existing group. // This can be either an ingress or an egress rule (see the notes // about neutron.RuleInfoV2). func (n *Neutron) addSecurityGroupRule(ruleId string, rule neutron.RuleInfoV2) error { if err := n.ProcessFunctionHook(n, ruleId, rule); err != nil { return err } return n.neutronModel.AddSecurityGroupRule(ruleId, rule) } // hasSecurityGroupRule returns whether the given group contains the given rule. func (n *Neutron) hasSecurityGroupRule(groupId, ruleId string) bool { return n.neutronModel.HasSecurityGroupRule(groupId, ruleId) } // securityGroupRule retrieves an existing rule by ID. func (n *Neutron) securityGroupRule(ruleId string) (*neutron.SecurityGroupRuleV2, error) { if err := n.ProcessFunctionHook(n, ruleId); err != nil { return nil, err } return n.neutronModel.SecurityGroupRule(ruleId) } // removeSecurityGroupRule deletes an existing rule from its group. func (n *Neutron) removeSecurityGroupRule(ruleId string) error { if err := n.ProcessFunctionHook(n, ruleId); err != nil { return err } return n.neutronModel.RemoveSecurityGroupRule(ruleId) } // addFloatingIP creates a new floating IP address in the pool. func (n *Neutron) addFloatingIP(ip neutron.FloatingIPV2) error { if err := n.ProcessFunctionHook(n, ip); err != nil { return err } return n.neutronModel.AddFloatingIP(ip) } // hasFloatingIP returns whether the given floating IP address exists. func (n *Neutron) hasFloatingIP(address string) bool { return n.neutronModel.HasFloatingIP(address) } // floatingIP retrieves the floating IP by ID. func (n *Neutron) floatingIP(ipId string) (*neutron.FloatingIPV2, error) { if err := n.ProcessFunctionHook(n, ipId); err != nil { return nil, err } return n.neutronModel.FloatingIP(ipId) } // floatingIPByAddr retrieves the floating IP by address. func (n *Neutron) floatingIPByAddr(address string) (*neutron.FloatingIPV2, error) { if err := n.ProcessFunctionHook(n, address); err != nil { return nil, err } return n.neutronModel.FloatingIPByAddr(address) } // allFloatingIPs returns a list of all created floating IPs. func (n *Neutron) allFloatingIPs() []neutron.FloatingIPV2 { return n.neutronModel.AllFloatingIPs() } // removeFloatingIP deletes an existing floating IP by ID. func (n *Neutron) removeFloatingIP(ipId string) error { if err := n.ProcessFunctionHook(n, ipId); err != nil { return err } return n.neutronModel.RemoveFloatingIP(ipId) } // filter is used internally by matchNetworks. type filter map[string]string // matchNetworks returns a list of matching networks, after applying the // given filter. Each separate filter is combined with a logical AND. // Each filter can have only one value. A nil filter matches all networks. // // This is tested to match OpenStack behavior. Regular expression // matching is supported for FilterNetwork only, and the supported // syntax is limited to whatever DB backend is used (see SQL // REGEXP/RLIKE). // // Example: // // f := filter{ // neutron.FilterRouterExternal: true, // neutron.FilterNetwork: `foo.*`, // } // // This will match all external neworks with names starting // with "foo". func (n *Neutron) matchNetworks(f filter) ([]neutron.NetworkV2, error) { networks := n.neutronModel.AllNetworks() if len(f) == 0 { return networks, nil } if external := f[neutron.FilterRouterExternal]; external != "" { matched := []neutron.NetworkV2{} externalBool, err := strconv.ParseBool(external) if err != nil { return nil, err } for _, network := range networks { if network.External == externalBool { matched = append(matched, network) } } if len(matched) == 0 { // no match, so no need to look further return nil, nil } networks = matched } if nameRegExp := f[neutron.FilterNetwork]; nameRegExp != "" { matched := []neutron.NetworkV2{} regExp, err := regexp.Compile(nameRegExp) if err != nil { return nil, err } for _, network := range networks { if regExp.MatchString(network.Name) { matched = append(matched, network) } } if len(matched) == 0 { // no match, so no need to look further return nil, nil } networks = matched } return networks, nil } // allNetworks returns a list of all existing networks. func (n *Neutron) allNetworks(f filter) ([]neutron.NetworkV2, error) { return n.matchNetworks(f) } // network retrieves the network by ID. func (n *Neutron) network(networkId string) (*neutron.NetworkV2, error) { if err := n.ProcessFunctionHook(n, networkId); err != nil { return nil, err } return n.neutronModel.Network(networkId) } // addNetwork creates a new network. func (n *Neutron) addNetwork(network neutron.NetworkV2) error { if err := n.ProcessFunctionHook(n, network); err != nil { return err } return n.neutronModel.AddNetwork(network) } // removeNetwork deletes an existing group. func (n *Neutron) removeNetwork(netId string) error { if err := n.ProcessFunctionHook(n, netId); err != nil { return err } return n.neutronModel.RemoveNetwork(netId) } // allSubnets returns a list of all existing subnets. func (n *Neutron) allSubnets() (subnets []neutron.SubnetV2) { for _, sub := range n.subnets { subnets = append(subnets, sub) } return subnets } // subnet retrieves the subnet by ID. func (n *Neutron) subnet(subnetId string) (*neutron.SubnetV2, error) { if err := n.ProcessFunctionHook(n, subnetId); err != nil { return nil, err } subnet, ok := n.subnets[subnetId] if !ok { return nil, testservices.NewSubnetNotFoundError(subnetId) } return &subnet, nil } // addSubnet creates a new subnet. func (n *Neutron) addSubnet(subnet neutron.SubnetV2) error { if err := n.ProcessFunctionHook(n, subnet); err != nil { return err } if _, err := n.subnet(subnet.Id); err == nil { return testservices.NewSubnetAlreadyExistsError(subnet.Id) } subnet.TenantId = n.TenantId n.subnets[subnet.Id] = subnet return nil } // removeSubnet deletes an existing subnet. func (n *Neutron) removeSubnet(subnetId string) error { if err := n.ProcessFunctionHook(n, subnetId); err != nil { return err } if _, err := n.subnet(subnetId); err != nil { return err } delete(n.subnets, subnetId) return nil } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/neutronservice/service_http.go000066400000000000000000000466501414605405700307530ustar00rootroot00000000000000// Neutron double testing service - HTTP API implementation package neutronservice import ( "crypto/rand" "encoding/json" "fmt" "io" "io/ioutil" "net/http" "path" "strconv" "strings" "gopkg.in/goose.v1/neutron" "gopkg.in/goose.v1/testservices" "gopkg.in/goose.v1/testservices/identityservice" ) const ( authToken = "X-Auth-Token" apiFloatingIPsV2 = "/v2.0/" + neutron.ApiFloatingIPsV2 apiNetworksV2 = "/v2.0/" + neutron.ApiNetworksV2 apiSubnetsV2 = "/v2.0/" + neutron.ApiSubnetsV2 apiSecurityGroupsV2 = "/v2.0/" + neutron.ApiSecurityGroupsV2 apiSecurityGroupRulesV2 = "/v2.0/" + neutron.ApiSecurityGroupRulesV2 ) // errorResponse defines a single HTTP error response. type errorResponse struct { code int body string contentType string errorText string headers map[string]string neutron *Neutron } // verbatim real Neutron responses (as errors). var ( errUnauthorized = &errorResponse{ http.StatusUnauthorized, `401 Unauthorized This server could not verify that you are authorized to access the ` + `document you requested. Either you supplied the wrong ` + `credentials (e.g., bad password), or your browser does ` + `not understand how to supply the credentials required. Authentication required `, "text/plain; charset=UTF-8", "unauthorized request", nil, nil, } errForbidden = &errorResponse{ http.StatusForbidden, `{"forbidden": {"message": "Policy doesn't allow compute_extension:` + `flavormanage to be performed.", "code": 403}}`, "application/json; charset=UTF-8", "forbidden flavors request", nil, nil, } errBadRequestMalformedURL = &errorResponse{ http.StatusBadRequest, `{"badRequest": {"message": "Malformed request url", "code": 400}}`, "application/json; charset=UTF-8", "bad request base path or URL", nil, nil, } errBadRequestIncorrect = &errorResponse{ http.StatusBadRequest, `{"badRequest": {"message": "The server could not comply with the ` + `request since it is either malformed or otherwise incorrect.", "code": 400}}`, "application/json; charset=UTF-8", "bad request URL", nil, nil, } errNotFound = &errorResponse{ http.StatusNotFound, `404 Not Found The resource could not be found. `, "text/plain; charset=UTF-8", "resource not found", nil, nil, } errNotFoundJSON = &errorResponse{ http.StatusNotFound, `{"itemNotFound": {"message": "The resource could not be found.", "code": 404}}`, "application/json; charset=UTF-8", "resource not found", nil, nil, } errNotFoundJSONSG = &errorResponse{ http.StatusNotFound, `{"itemNotFound": {"message": "Security group $ID$ not found.", "code": 404}}`, "application/json; charset=UTF-8", "", nil, nil, } errNotFoundJSONSGR = &errorResponse{ http.StatusNotFound, `{"itemNotFound": {"message": "Rule ($ID$) not found.", "code": 404}}`, "application/json; charset=UTF-8", "security rule not found", nil, nil, } errMultipleChoices = &errorResponse{ http.StatusMultipleChoices, `{"choices": [{"status": "CURRENT", "media-types": [{"base": ` + `"application/xml", "type": "application/vnd.openstack.compute+` + `xml;version=2"}, {"base": "application/json", "type": "application/` + `vnd.openstack.compute+json;version=2"}], "id": "v2.0", "links": ` + `[{"href": "$ENDPOINT$$URL$", "rel": "self"}]}]}`, "application/json", "multiple URL redirection choices", nil, nil, } errNoVersion = &errorResponse{ http.StatusOK, `{"versions": [{"status": "CURRENT", "id": "v2.0", "links": [{"href": "v2.0", "rel": "self"}]}]}`, "application/json", "no version specified in URL", nil, nil, } errNotImplemented = &errorResponse{ http.StatusNotImplemented, "501 Not Implemented", "text/plain; charset=UTF-8", "not implemented", nil, nil, } errNoGroupId = &errorResponse{ errorText: "no security group id given", } errRateLimitExceeded = &errorResponse{ http.StatusRequestEntityTooLarge, "", "text/plain; charset=UTF-8", "too many requests", // RFC says that Retry-After should be an int, but we don't want to wait an entire second during the test suite. map[string]string{"Retry-After": "0.001"}, nil, } errNoMoreFloatingIPs = &errorResponse{ http.StatusNotFound, "Zero floating ips available.", "text/plain; charset=UTF-8", "zero floating ips available", nil, nil, } errIPLimitExceeded = &errorResponse{ http.StatusRequestEntityTooLarge, "Maximum number of floating ips exceeded.", "text/plain; charset=UTF-8", "maximum number of floating ips exceeded", nil, nil, } ) func (e *errorResponse) Error() string { return e.errorText } // requestBody returns the body for the error response, replacing // $ENDPOINT$, $URL$, $ID$, and $ERROR$ in e.body with the values from // the request. func (e *errorResponse) requestBody(r *http.Request) []byte { url := strings.TrimLeft(r.URL.Path, "/") body := e.body if body != "" { if e.neutron != nil { body = strings.Replace(body, "$ENDPOINT$", e.neutron.endpointURL(true, "/"), -1) } body = strings.Replace(body, "$URL$", url, -1) body = strings.Replace(body, "$ERROR$", e.Error(), -1) if slash := strings.LastIndex(url, "/"); slash != -1 { body = strings.Replace(body, "$ID$", url[slash+1:], -1) } } return []byte(body) } func (e *errorResponse) ServeHTTP(w http.ResponseWriter, r *http.Request) { if e.contentType != "" { w.Header().Set("Content-Type", e.contentType) } body := e.requestBody(r) if e.headers != nil { for h, v := range e.headers { w.Header().Set(h, v) } } // workaround for https://code.google.com/p/go/issues/detail?id=4454 w.Header().Set("Content-Length", strconv.Itoa(len(body))) if e.code != 0 { w.WriteHeader(e.code) } if len(body) > 0 { w.Write(body) } } type neutronHandler struct { n *Neutron method func(n *Neutron, w http.ResponseWriter, r *http.Request) error } func userInfo(i identityservice.IdentityService, r *http.Request) (*identityservice.UserInfo, error) { return i.FindUser(r.Header.Get(authToken)) } func (h *neutronHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { path := r.URL.Path // handle invalid X-Auth-Token header _, err := userInfo(h.n.IdentityService, r) if err != nil { errUnauthorized.ServeHTTP(w, r) return } // handle trailing slash in the path if strings.HasSuffix(path, "/") && path != "/" { errNotFound.ServeHTTP(w, r) return } err = h.method(h.n, w, r) if err == nil { return } var resp http.Handler if err == testservices.RateLimitExceededError { resp = errRateLimitExceeded } else if err == testservices.NoMoreFloatingIPs { resp = errNoMoreFloatingIPs } else if err == testservices.IPLimitExceeded { resp = errIPLimitExceeded } else { resp, _ = err.(http.Handler) if resp == nil { code, encodedErr := errorJSONEncode(err) resp = &errorResponse{ code, encodedErr, "application/json", err.Error(), nil, h.n, } } } resp.ServeHTTP(w, r) } func writeResponse(w http.ResponseWriter, code int, body []byte) { // workaround for https://code.google.com/p/go/issues/detail?id=4454 w.Header().Set("Content-Length", strconv.Itoa(len(body))) w.WriteHeader(code) w.Write(body) } // sendJSON sends the specified response serialized as JSON. func sendJSON(code int, resp interface{}, w http.ResponseWriter, r *http.Request) error { data, err := json.Marshal(resp) if err != nil { return err } writeResponse(w, code, data) return nil } func (n *Neutron) handler(method func(n *Neutron, w http.ResponseWriter, r *http.Request) error) http.Handler { return &neutronHandler{n, method} } func (n *Neutron) handleRoot(w http.ResponseWriter, r *http.Request) error { if r.URL.Path == "/" { return errNoVersion } return errMultipleChoices } func (n *Neutron) HandleRoot(w http.ResponseWriter, r *http.Request) { n.handler((*Neutron).handleRoot).ServeHTTP(w, r) } // newUUID generates a random UUID conforming to RFC 4122. func newUUID() (string, error) { uuid := make([]byte, 16) if _, err := io.ReadFull(rand.Reader, uuid); err != nil { return "", err } uuid[8] = uuid[8]&^0xc0 | 0x80 // variant bits; see section 4.1.1. uuid[6] = uuid[6]&^0xf0 | 0x40 // version 4; see section 4.1.3. return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]), nil } // processGroupId returns the group if one is specified in the given request, // either by id or by ?name. If there was no group id specified in the path, // it returns errNoGroupId func (n *Neutron) processGroupId(w http.ResponseWriter, r *http.Request) (*neutron.SecurityGroupV2, error) { groupId := path.Base(r.URL.Path) apiFunc := path.Base(apiSecurityGroupsV2) if groupId != apiFunc { group, err := n.securityGroup(groupId) if err != nil { return nil, errNotFoundJSONSG } return group, nil } return nil, errNoGroupId } // handleSecurityGroups handles the /v2.0/security-groups HTTP API. func (n *Neutron) handleSecurityGroups(w http.ResponseWriter, r *http.Request) error { switch r.Method { case "GET": group, err := n.processGroupId(w, r) if err == errNoGroupId { groups := []neutron.SecurityGroupV2{} query := r.URL.Query() if len(query) == 1 { secGroupName := query["name"][0] groups, err = n.securityGroupByName(secGroupName) if err != nil { return err } } else { groups = n.allSecurityGroups() } resp := struct { Groups []neutron.SecurityGroupV2 `json:"security_groups"` }{groups} return sendJSON(http.StatusOK, resp, w, r) } if err != nil { return err } resp := struct { Group neutron.SecurityGroupV2 `json:"security_group"` }{*group} return sendJSON(http.StatusOK, resp, w, r) case "POST": _, err := n.processGroupId(w, r) if err != errNoGroupId { return errNotFound } body, err := ioutil.ReadAll(r.Body) if err != nil || len(body) == 0 { return errBadRequestIncorrect } var req struct { Group struct { Name string Description string } `json:"security_group"` } if err := json.Unmarshal(body, &req); err != nil { return err } else { n.nextGroupId++ nextId := strconv.Itoa(n.nextGroupId) err = n.addSecurityGroup(neutron.SecurityGroupV2{ Id: nextId, Name: req.Group.Name, Description: req.Group.Description, TenantId: n.TenantId, }) if err != nil { return err } group, err := n.securityGroup(nextId) if err != nil { return err } var resp struct { Group neutron.SecurityGroupV2 `json:"security_group"` } resp.Group = *group return sendJSON(http.StatusCreated, resp, w, r) } case "PUT": group, err := n.processGroupId(w, r) if err == errNoGroupId { return errNotFound } var req struct { Group struct { Name string Description string } `json:"security_group"` } body, err := ioutil.ReadAll(r.Body) if err != nil || len(body) == 0 { return errBadRequestIncorrect } if err := json.Unmarshal(body, &req); err != nil { return err } err = n.updateSecurityGroup(neutron.SecurityGroupV2{ Id: group.Id, Name: req.Group.Name, Description: req.Group.Description, TenantId: group.TenantId, }) if err != nil { return err } group, err = n.securityGroup(group.Id) if err != nil { return err } var resp struct { Group neutron.SecurityGroupV2 `json:"security_group"` } resp.Group = *group return sendJSON(http.StatusOK, resp, w, r) case "DELETE": if group, err := n.processGroupId(w, r); group != nil { if err := n.removeSecurityGroup(group.Id); err != nil { return err } writeResponse(w, http.StatusNoContent, nil) return nil } else if err == errNoGroupId { return errNotFound } else { return err } } return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path) } // handleSecurityGroupRules handles the /v2.0/security-group-rules HTTP API. func (n *Neutron) handleSecurityGroupRules(w http.ResponseWriter, r *http.Request) error { switch r.Method { case "GET": return errNotFoundJSON case "POST": ruleId := path.Base(r.URL.Path) apiFunc := path.Base(apiSecurityGroupRulesV2) if ruleId != apiFunc { return errNotFound } body, err := ioutil.ReadAll(r.Body) if err != nil || len(body) == 0 { return errBadRequestIncorrect } var req struct { Rule neutron.RuleInfoV2 `json:"security_group_rule"` } if err = json.Unmarshal(body, &req); err != nil { return err } inrule := req.Rule // set default EthernetType for correct comparison // TODO: we should probably have a neutronmodel func to parse and set default values // and/or move the duplicate check there if inrule.EthernetType == "" { inrule.EthernetType = "IPv4" } group, err := n.securityGroup(inrule.ParentGroupId) if err != nil { return err // TODO: should be a 4XX error with details } for _, r := range group.Rules { // TODO: this logic is actually wrong, not what neutron does at all // why are we reimplementing half of neutron/api/openstack in go again? if r.IPProtocol != nil && *r.IPProtocol == inrule.IPProtocol && r.PortRangeMax != nil && *r.PortRangeMax == inrule.PortRangeMax && r.PortRangeMin != nil && *r.PortRangeMin == inrule.PortRangeMin && r.RemoteIPPrefix != "" && r.RemoteIPPrefix == inrule.RemoteIPPrefix && r.EthernetType != "" && r.EthernetType == inrule.EthernetType { // TODO: Use a proper helper and sane error type return &errorResponse{ http.StatusBadRequest, fmt.Sprintf(`{"badRequest": {"message": "This rule already exists in group %s", "code": 400}}`, group.Id), "application/json; charset=UTF-8", "rule already exists", nil, nil, } } } n.nextRuleId++ nextId := strconv.Itoa(n.nextRuleId) err = n.addSecurityGroupRule(nextId, req.Rule) if err != nil { return err } rule, err := n.securityGroupRule(nextId) if err != nil { return err } var resp struct { Rule neutron.SecurityGroupRuleV2 `json:"security_group_rule"` } resp.Rule = *rule return sendJSON(http.StatusCreated, resp, w, r) case "PUT": return errNotFound case "DELETE": ruleId := path.Base(r.URL.Path) apiFunc := path.Base(apiSecurityGroupRulesV2) if ruleId != apiFunc { if _, err := n.securityGroupRule(ruleId); err != nil { return errNotFoundJSONSGR } if err := n.removeSecurityGroupRule(ruleId); err != nil { return err } writeResponse(w, http.StatusNoContent, nil) return nil } return errNotFound } return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path) } // handleFloatingIPs handles the v2/floatingips HTTP API. func (n *Neutron) handleFloatingIPs(w http.ResponseWriter, r *http.Request) error { switch r.Method { case "GET": ipId := path.Base(r.URL.Path) apiFunc := path.Base(apiFloatingIPsV2) if ipId != apiFunc { fip, err := n.floatingIP(ipId) if err != nil { return errNotFoundJSON } resp := struct { IP neutron.FloatingIPV2 `json:"floatingip"` }{*fip} return sendJSON(http.StatusOK, resp, w, r) } fips := n.allFloatingIPs() if len(fips) == 0 { fips = []neutron.FloatingIPV2{} } resp := struct { IPs []neutron.FloatingIPV2 `json:"floatingips"` }{fips} return sendJSON(http.StatusOK, resp, w, r) case "POST": ipId := path.Base(r.URL.Path) apiFunc := path.Base(apiFloatingIPsV2) if ipId != apiFunc { return errNotFound } body, err := ioutil.ReadAll(r.Body) if err != nil || len(body) == 0 { return errBadRequestIncorrect } var req struct { FIP neutron.FloatingIPV2 `json:"floatingip"` } if err := json.Unmarshal(body, &req); err != nil { return err } extNetwork, err := n.network(req.FIP.FloatingNetworkId) if err != nil { return err } if !extNetwork.External { return errNotFound } n.nextIPId++ addr := fmt.Sprintf("10.0.0.%d", n.nextIPId) nextId := strconv.Itoa(n.nextIPId) fip := neutron.FloatingIPV2{FixedIP: "", Id: nextId, IP: addr, FloatingNetworkId: extNetwork.Id} err = n.addFloatingIP(fip) if err != nil { return err } resp := struct { FloatingIPV2 neutron.FloatingIPV2 `json:"floatingip"` }{fip} return sendJSON(http.StatusCreated, resp, w, r) case "PUT": return errNotFound case "DELETE": ipId := path.Base(r.URL.Path) apiFunc := path.Base(apiFloatingIPsV2) if ipId != apiFunc { if err := n.removeFloatingIP(ipId); err == nil { writeResponse(w, http.StatusNoContent, nil) return nil } return errNotFoundJSON } return errNotFound } return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path) } // handleNetworks handles the v2/networks HTTP API. func (n *Neutron) handleNetworks(w http.ResponseWriter, r *http.Request) error { switch r.Method { case "GET": networkId := path.Base(r.URL.Path) apiFunc := path.Base(apiNetworksV2) if networkId != apiFunc { network, err := n.network(networkId) if err != nil { return errNotFoundJSON } resp := struct { Network neutron.NetworkV2 `json:"network"` }{*network} return sendJSON(http.StatusOK, resp, w, r) } f := make(filter) if err := r.ParseForm(); err == nil && len(r.Form) > 0 { for filterKey, filterValues := range r.Form { for _, value := range filterValues { f[filterKey] = value } } } nets, err := n.allNetworks(f) if err != nil { return err } if len(nets) == 0 { nets = []neutron.NetworkV2{} } resp := struct { Network []neutron.NetworkV2 `json:"networks"` }{nets} return sendJSON(http.StatusOK, resp, w, r) default: return errNotFound } return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path) } // handleNetworks handles the v2/subnets HTTP API. func (n *Neutron) handleSubnets(w http.ResponseWriter, r *http.Request) error { switch r.Method { case "GET": subnetId := path.Base(r.URL.Path) apiFunc := path.Base(apiSubnetsV2) if subnetId != apiFunc { subnet, err := n.subnet(subnetId) if err != nil { return errNotFoundJSON } resp := struct { Subnet neutron.SubnetV2 `json:"subnet"` }{*subnet} return sendJSON(http.StatusOK, resp, w, r) } subnets := n.allSubnets() if len(subnets) == 0 { subnets = []neutron.SubnetV2{} } resp := struct { Subnets []neutron.SubnetV2 `json:"subnets"` }{subnets} return sendJSON(http.StatusOK, resp, w, r) default: return errNotFound } return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path) } // SetupHTTP attaches all the needed handlers to provide the HTTP API. func (n *Neutron) SetupHTTP(mux *http.ServeMux) { handlers := map[string]http.Handler{ "/$v": errNoVersion, "/$v/": errBadRequestMalformedURL, "/$v/security-groups": n.handler((*Neutron).handleSecurityGroups), "/$v/security-groups/": n.handler((*Neutron).handleSecurityGroups), "/$v/security-group-rules": n.handler((*Neutron).handleSecurityGroupRules), "/$v/security-group-rules/": n.handler((*Neutron).handleSecurityGroupRules), "/$v/floatingips": n.handler((*Neutron).handleFloatingIPs), "/$v/floatingips/": n.handler((*Neutron).handleFloatingIPs), "/$v/networks": n.handler((*Neutron).handleNetworks), "/$v/networks/": n.handler((*Neutron).handleNetworks), "/$v/subnets": n.handler((*Neutron).handleSubnets), "/$v/subnets/": n.handler((*Neutron).handleSubnets), } for path, h := range handlers { path = strings.Replace(path, "$v", n.VersionPath, 1) mux.Handle(path, h) } } func (n *Neutron) SetupRootHandler(mux *http.ServeMux) { mux.Handle("/", n.handler((*Neutron).handleRoot)) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/neutronservice/service_http_test.go000066400000000000000000000613061414605405700320050ustar00rootroot00000000000000// Neutron double testing service - HTTP API tests package neutronservice import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "strconv" "strings" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/neutron" "gopkg.in/goose.v1/testing/httpsuite" "gopkg.in/goose.v1/testservices/identityservice" "gopkg.in/goose.v1/testservices/neutronmodel" ) type NeutronHTTPSuite struct { httpsuite.HTTPSuite service *Neutron token string } var _ = gc.Suite(&NeutronHTTPSuite{}) type NeutronHTTPSSuite struct { httpsuite.HTTPSuite service *Neutron token string } var _ = gc.Suite(&NeutronHTTPSSuite{HTTPSuite: httpsuite.HTTPSuite{UseTLS: true}}) func (s *NeutronHTTPSuite) SetUpSuite(c *gc.C) { s.HTTPSuite.SetUpSuite(c) identityDouble := identityservice.NewUserPass() userInfo := identityDouble.AddUser("fred", "secret", "tenant", "default") s.token = userInfo.Token s.service = New(s.Server.URL, versionPath, userInfo.TenantId, region, identityDouble, nil) s.service.AddNeutronModel(neutronmodel.New()) } func (s *NeutronHTTPSuite) TearDownSuite(c *gc.C) { s.HTTPSuite.TearDownSuite(c) } func (s *NeutronHTTPSuite) SetUpTest(c *gc.C) { s.HTTPSuite.SetUpTest(c) s.service.SetupHTTP(s.Mux) // this is otherwise handled not directly by neutron test service // but by openstack that tries for / before. s.Mux.Handle("/", s.service.handler((*Neutron).handleRoot)) } func (s *NeutronHTTPSuite) TearDownTest(c *gc.C) { s.HTTPSuite.TearDownTest(c) } // assertJSON asserts the passed http.Response's body can be // unmarshalled into the given expected object, populating it with the // successfully parsed data. func assertJSON(c *gc.C, resp *http.Response, expected interface{}) { body, err := ioutil.ReadAll(resp.Body) defer resp.Body.Close() c.Assert(err, gc.IsNil) err = json.Unmarshal(body, &expected) c.Assert(err, gc.IsNil) } // assertBody asserts the passed http.Response's body matches the // expected response, replacing any variables in the expected body. func assertBody(c *gc.C, resp *http.Response, expected *errorResponse) { body, err := ioutil.ReadAll(resp.Body) defer resp.Body.Close() c.Assert(err, gc.IsNil) expBody := expected.requestBody(resp.Request) // cast to string for easier asserts debugging c.Assert(string(body), gc.Equals, string(expBody)) } // sendRequest constructs an HTTP request from the parameters and // sends it, returning the response or an error. func (s *NeutronHTTPSuite) sendRequest(method, url string, body []byte, headers http.Header) (*http.Response, error) { if !strings.HasPrefix(url, "http") { url = "http://" + s.service.Hostname + strings.TrimLeft(url, "/") } req, err := http.NewRequest(method, url, bytes.NewReader(body)) if err != nil { return nil, err } for header, values := range headers { for _, value := range values { req.Header.Add(header, value) } } // workaround for https://code.google.com/p/go/issues/detail?id=4454 req.Header.Set("Content-Length", strconv.Itoa(len(body))) return http.DefaultClient.Do(req) } // authRequest is a shortcut for sending requests with pre-set token // header and correct version prefix and tenant ID in the URL. func (s *NeutronHTTPSuite) authRequest(method, path string, body []byte, headers http.Header) (*http.Response, error) { if headers == nil { headers = make(http.Header) } headers.Set(authToken, s.token) url := s.service.endpointURL(true, path) return s.sendRequest(method, url, body, headers) } // jsonRequest serializes the passed body object to JSON and sends a // the request with authRequest(). func (s *NeutronHTTPSuite) jsonRequest(method, path string, body interface{}, headers http.Header) (*http.Response, error) { jsonBody, err := json.Marshal(body) if err != nil { return nil, err } return s.authRequest(method, path, jsonBody, headers) } // setHeader creates http.Header map, sets the given header, and // returns the map. func setHeader(header, value string) http.Header { h := make(http.Header) h.Set(header, value) return h } // SimpleTest defines a simple request without a body and expected response. type SimpleTest struct { unauth bool method string url string headers http.Header expect *errorResponse } func (s *NeutronHTTPSuite) simpleTests() []SimpleTest { var simpleTests = []SimpleTest{ { unauth: true, method: "GET", url: "/any", headers: make(http.Header), expect: errUnauthorized, }, { unauth: true, method: "POST", url: "/any", headers: setHeader(authToken, "phony"), expect: errUnauthorized, }, { unauth: true, method: "GET", url: "/any", headers: setHeader(authToken, s.token), expect: errMultipleChoices, }, { unauth: true, method: "POST", url: "/any/unknown/one", headers: setHeader(authToken, s.token), expect: errMultipleChoices, }, { unauth: true, method: "GET", url: versionPath + "/phony_token", headers: setHeader(authToken, s.token), expect: errBadRequestMalformedURL, }, { method: "GET", url: neutron.ApiSecurityGroupsV2 + "/42", expect: errNotFoundJSONSG, }, { method: "POST", url: neutron.ApiSecurityGroupsV2, expect: errBadRequestIncorrect, }, { method: "POST", url: neutron.ApiSecurityGroupsV2 + "/invalid", expect: errNotFound, }, { method: "PUT", url: neutron.ApiSecurityGroupsV2, expect: errNotFound, }, { method: "DELETE", url: neutron.ApiSecurityGroupsV2, expect: errNotFound, }, { method: "DELETE", url: neutron.ApiSecurityGroupsV2 + "/42", expect: errNotFoundJSONSG, }, { method: "GET", url: neutron.ApiSecurityGroupRulesV2, expect: errNotFoundJSON, }, { method: "GET", url: neutron.ApiSecurityGroupRulesV2 + "/invalid", expect: errNotFoundJSON, }, { method: "GET", url: neutron.ApiSecurityGroupRulesV2 + "/42", expect: errNotFoundJSON, }, { method: "POST", url: neutron.ApiSecurityGroupRulesV2, expect: errBadRequestIncorrect, }, { method: "POST", url: neutron.ApiSecurityGroupRulesV2 + "/invalid", expect: errNotFound, }, { method: "PUT", url: neutron.ApiSecurityGroupRulesV2, expect: errNotFound, }, { method: "PUT", url: neutron.ApiSecurityGroupRulesV2 + "/invalid", expect: errNotFound, }, { method: "DELETE", url: neutron.ApiSecurityGroupRulesV2, expect: errNotFound, }, { method: "DELETE", url: neutron.ApiSecurityGroupRulesV2 + "/42", expect: errNotFoundJSONSGR, }, { method: "GET", url: neutron.ApiFloatingIPsV2 + "/42", expect: errNotFoundJSON, }, { method: "POST", url: neutron.ApiFloatingIPsV2 + "/invalid", expect: errNotFound, }, { method: "PUT", url: neutron.ApiFloatingIPsV2, expect: errNotFound, }, { method: "PUT", url: neutron.ApiFloatingIPsV2 + "/invalid", expect: errNotFound, }, { method: "DELETE", url: neutron.ApiFloatingIPsV2, expect: errNotFound, }, { method: "DELETE", url: neutron.ApiFloatingIPsV2 + "/invalid", expect: errNotFoundJSON, }, { method: "GET", url: neutron.ApiNetworksV2 + "/42", expect: errNotFoundJSON, }, { method: "POST", url: neutron.ApiNetworksV2 + "/invalid", expect: errNotFound, }, { method: "PUT", url: neutron.ApiNetworksV2, expect: errNotFound, }, { method: "PUT", url: neutron.ApiNetworksV2 + "/invalid", expect: errNotFound, }, { method: "DELETE", url: neutron.ApiNetworksV2, expect: errNotFound, }, { method: "DELETE", url: neutron.ApiNetworksV2 + "/invalid", expect: errNotFound, }, { method: "GET", url: neutron.ApiSubnetsV2 + "/42", expect: errNotFoundJSON, }, { method: "POST", url: neutron.ApiSubnetsV2 + "/invalid", expect: errNotFound, }, { method: "PUT", url: neutron.ApiSubnetsV2, expect: errNotFound, }, { method: "PUT", url: neutron.ApiSubnetsV2 + "/invalid", expect: errNotFound, }, { method: "DELETE", url: neutron.ApiSubnetsV2, expect: errNotFound, }, { method: "DELETE", url: neutron.ApiSubnetsV2 + "/invalid", expect: errNotFound, }, } return simpleTests } func (s *NeutronHTTPSuite) TestSimpleRequestTests(c *gc.C) { simpleTests := s.simpleTests() for i, t := range simpleTests { c.Logf("#%d. %s %s -> %d", i, t.method, t.url, t.expect.code) if t.headers == nil { t.headers = make(http.Header) t.headers.Set(authToken, s.token) } var ( resp *http.Response err error ) if t.unauth { resp, err = s.sendRequest(t.method, t.url, nil, t.headers) } else { resp, err = s.authRequest(t.method, t.url, nil, t.headers) } c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, t.expect.code) assertBody(c, resp, t.expect) } fmt.Printf("total: %d\n", len(simpleTests)) } func (s *NeutronHTTPSuite) TestNewUUID(c *gc.C) { uuid, err := newUUID() c.Assert(err, gc.IsNil) var p1, p2, p3, p4, p5 string num, err := fmt.Sscanf(uuid, "%8x-%4x-%4x-%4x-%12x", &p1, &p2, &p3, &p4, &p5) c.Assert(err, gc.IsNil) c.Assert(num, gc.Equals, 5) uuid2, err := newUUID() c.Assert(err, gc.IsNil) c.Assert(uuid2, gc.Not(gc.Equals), uuid) } func (s *NeutronHTTPSuite) TestGetSecurityGroups(c *gc.C) { // There is always a default security group. groups := s.service.allSecurityGroups() c.Assert(groups, gc.HasLen, 1) var expected struct { Groups []neutron.SecurityGroupV2 `json:"security_groups"` } resp, err := s.authRequest("GET", neutron.ApiSecurityGroupsV2, nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) c.Assert(expected.Groups, gc.HasLen, 1) groups = []neutron.SecurityGroupV2{ { Id: "1", Name: "group 1", TenantId: s.service.TenantId, Rules: []neutron.SecurityGroupRuleV2{}, }, { Id: "2", Name: "group 2", TenantId: s.service.TenantId, Rules: []neutron.SecurityGroupRuleV2{}, }, } for _, group := range groups { err := s.service.addSecurityGroup(group) c.Assert(err, gc.IsNil) defer s.service.removeSecurityGroup(group.Id) } groups[0].Rules = defaultSecurityGroupRules(groups[0].Id, groups[0].TenantId) groups[1].Rules = defaultSecurityGroupRules(groups[1].Id, groups[1].TenantId) resp, err = s.authRequest("GET", neutron.ApiSecurityGroupsV2, nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) c.Assert(expected.Groups, gc.HasLen, len(groups)+1) checkGroupsInList(c, groups, expected.Groups) var expectedGroup struct { Group neutron.SecurityGroupV2 `json:"security_group"` } url := fmt.Sprintf("%s/%s", neutron.ApiSecurityGroupsV2, "1") resp, err = s.authRequest("GET", url, nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expectedGroup) c.Assert(expectedGroup.Group, gc.DeepEquals, groups[0]) } func defaultSecurityGroupRules(groupId, tenantId string) []neutron.SecurityGroupRuleV2 { id, _ := strconv.Atoi(groupId) id1 := id * 999 id2 := id * 998 return []neutron.SecurityGroupRuleV2{ { Direction: "egress", EthernetType: "IPv4", Id: strconv.Itoa(id1), TenantId: tenantId, ParentGroupId: groupId, }, { Direction: "egress", EthernetType: "IPv6", Id: strconv.Itoa(id2), TenantId: tenantId, ParentGroupId: groupId, }, } } func (s *NeutronHTTPSuite) TestAddSecurityGroup(c *gc.C) { group := neutron.SecurityGroupV2{ Id: "1", Name: "group 1", Description: "desc", TenantId: s.service.TenantId, Rules: []neutron.SecurityGroupRuleV2{}, } _, err := s.service.securityGroup(group.Id) c.Assert(err, gc.NotNil) group.Rules = defaultSecurityGroupRules(group.Id, group.TenantId) var req struct { Group struct { Name string `json:"name"` Description string `json:"description"` } `json:"security_group"` } req.Group.Name = group.Name req.Group.Description = group.Description var expected struct { Group neutron.SecurityGroupV2 `json:"security_group"` } resp, err := s.jsonRequest("POST", neutron.ApiSecurityGroupsV2, req, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusCreated) assertJSON(c, resp, &expected) c.Assert(expected.Group, gc.DeepEquals, group) err = s.service.removeSecurityGroup(group.Id) c.Assert(err, gc.IsNil) } func (s *NeutronHTTPSuite) TestDeleteSecurityGroup(c *gc.C) { group := neutron.SecurityGroupV2{Id: "1", Name: "group 1", TenantId: s.service.TenantId} _, err := s.service.securityGroup(group.Id) c.Assert(err, gc.NotNil) err = s.service.addSecurityGroup(group) c.Assert(err, gc.IsNil) defer s.service.removeSecurityGroup(group.Id) url := fmt.Sprintf("%s/%s", neutron.ApiSecurityGroupsV2, "1") resp, err := s.authRequest("DELETE", url, nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusNoContent) _, err = s.service.securityGroup(group.Id) c.Assert(err, gc.NotNil) } func (s *NeutronHTTPSuite) TestAddSecurityGroupRule(c *gc.C) { group1 := neutron.SecurityGroupV2{Id: "1", Name: "src", TenantId: s.service.TenantId} group2 := neutron.SecurityGroupV2{Id: "2", Name: "tgt", TenantId: s.service.TenantId} err := s.service.addSecurityGroup(group1) c.Assert(err, gc.IsNil) defer s.service.removeSecurityGroup(group1.Id) err = s.service.addSecurityGroup(group2) c.Assert(err, gc.IsNil) defer s.service.removeSecurityGroup(group2.Id) riIngress := neutron.RuleInfoV2{ ParentGroupId: "1", Direction: "ingress", PortRangeMax: 22, PortRangeMin: 22, IPProtocol: "tcp", RemoteIPPrefix: "1.2.3.4/5", } riIngress2 := neutron.RuleInfoV2{ ParentGroupId: "1", Direction: "ingress", PortRangeMax: 22, PortRangeMin: 22, IPProtocol: "tcp", RemoteIPPrefix: "2.3.4.5/6", } riEgress := neutron.RuleInfoV2{ ParentGroupId: group2.Id, Direction: "egress", PortRangeMax: 22, PortRangeMin: 22, IPProtocol: "tcp", RemoteIPPrefix: "5.4.3.2/1", } riIngress6 := neutron.RuleInfoV2{ ParentGroupId: "1", Direction: "ingress", PortRangeMax: 22, PortRangeMin: 22, IPProtocol: "tcp", RemoteIPPrefix: "2001:db8:42::/64", EthernetType: "IPv6", } rule1 := neutron.SecurityGroupRuleV2{ Id: "1", ParentGroupId: group1.Id, Direction: riIngress.Direction, PortRangeMax: &riIngress.PortRangeMax, PortRangeMin: &riIngress.PortRangeMin, IPProtocol: &riIngress.IPProtocol, RemoteIPPrefix: riIngress.RemoteIPPrefix, } rule2 := neutron.SecurityGroupRuleV2{ Id: "2", ParentGroupId: group1.Id, Direction: riIngress2.Direction, PortRangeMax: &riIngress2.PortRangeMax, PortRangeMin: &riIngress2.PortRangeMin, IPProtocol: &riIngress2.IPProtocol, RemoteIPPrefix: riIngress2.RemoteIPPrefix, } rule3 := neutron.SecurityGroupRuleV2{ Id: "3", ParentGroupId: group2.Id, Direction: riEgress.Direction, PortRangeMax: &riEgress.PortRangeMax, PortRangeMin: &riEgress.PortRangeMin, IPProtocol: &riEgress.IPProtocol, } rule6 := neutron.SecurityGroupRuleV2{ Id: "5", ParentGroupId: group1.Id, Direction: riIngress6.Direction, PortRangeMax: &riIngress6.PortRangeMax, PortRangeMin: &riIngress6.PortRangeMin, IPProtocol: &riIngress6.IPProtocol, RemoteIPPrefix: riIngress6.RemoteIPPrefix, } ok := s.service.hasSecurityGroupRule(group1.Id, rule1.Id) c.Assert(ok, gc.Equals, false) ok = s.service.hasSecurityGroupRule(group2.Id, rule2.Id) c.Assert(ok, gc.Equals, false) var req struct { Rule neutron.RuleInfoV2 `json:"security_group_rule"` } req.Rule = riIngress var expected struct { Rule neutron.SecurityGroupRuleV2 `json:"security_group_rule"` } resp, err := s.jsonRequest("POST", neutron.ApiSecurityGroupRulesV2, req, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusCreated) assertJSON(c, resp, &expected) c.Assert(expected.Rule.Id, gc.Equals, rule1.Id) c.Assert(expected.Rule.ParentGroupId, gc.Equals, rule1.ParentGroupId) c.Assert(*expected.Rule.PortRangeMax, gc.Equals, *rule1.PortRangeMax) c.Assert(*expected.Rule.PortRangeMin, gc.Equals, *rule1.PortRangeMin) c.Assert(*expected.Rule.IPProtocol, gc.Equals, *rule1.IPProtocol) c.Assert(expected.Rule.Direction, gc.Equals, rule1.Direction) c.Assert(expected.Rule.RemoteIPPrefix, gc.Equals, rule1.RemoteIPPrefix) // Attempt to create duplicate rule should fail resp, err = s.jsonRequest("POST", neutron.ApiSecurityGroupRulesV2, req, nil) c.Assert(resp.StatusCode, gc.Equals, http.StatusBadRequest) err = s.service.removeSecurityGroupRule(rule1.Id) c.Assert(err, gc.IsNil) // Attempt to create rule with all fields but RemoteIPPrefix identical should pass req.Rule = riIngress2 resp, err = s.jsonRequest("POST", neutron.ApiSecurityGroupRulesV2, req, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusCreated) err = s.service.removeSecurityGroupRule(rule2.Id) c.Assert(err, gc.IsNil) assertJSON(c, resp, &expected) c.Assert(expected.Rule.Id, gc.Equals, rule2.Id) c.Assert(expected.Rule.ParentGroupId, gc.Equals, rule2.ParentGroupId) c.Assert(*expected.Rule.PortRangeMax, gc.Equals, *rule2.PortRangeMax) c.Assert(*expected.Rule.PortRangeMin, gc.Equals, *rule2.PortRangeMin) c.Assert(*expected.Rule.IPProtocol, gc.Equals, *rule2.IPProtocol) c.Assert(expected.Rule.Direction, gc.Equals, rule2.Direction) c.Assert(expected.Rule.RemoteIPPrefix, gc.Equals, rule2.RemoteIPPrefix) req.Rule = riEgress resp, err = s.jsonRequest("POST", neutron.ApiSecurityGroupRulesV2, req, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusCreated) err = s.service.removeSecurityGroupRule(rule3.Id) c.Assert(err, gc.IsNil) assertJSON(c, resp, &expected) c.Assert(expected.Rule.Id, gc.Equals, rule3.Id) c.Assert(expected.Rule.ParentGroupId, gc.Equals, rule3.ParentGroupId) // Attempt to create rule with IPv6 RemoteIPPrefix without specifying EthernetType, should fail req.Rule = riIngress6 req.Rule.EthernetType = "" resp, err = s.jsonRequest("POST", neutron.ApiSecurityGroupRulesV2, req, nil) c.Assert(resp.StatusCode, gc.Equals, http.StatusBadRequest) // Attempt to create rule with IPv6 RemoteIPPrefix with correct EthernetType, should pass req.Rule = riIngress6 resp, err = s.jsonRequest("POST", neutron.ApiSecurityGroupRulesV2, req, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusCreated) err = s.service.removeSecurityGroupRule(rule6.Id) c.Assert(err, gc.IsNil) assertJSON(c, resp, &expected) c.Assert(expected.Rule.Id, gc.Equals, rule6.Id) c.Assert(expected.Rule.ParentGroupId, gc.Equals, rule6.ParentGroupId) } func (s *NeutronHTTPSuite) TestDeleteSecurityGroupRule(c *gc.C) { group1 := neutron.SecurityGroupV2{Id: "1", Name: "src", TenantId: s.service.TenantId} group2 := neutron.SecurityGroupV2{Id: "2", Name: "tgt", TenantId: s.service.TenantId} err := s.service.addSecurityGroup(group1) c.Assert(err, gc.IsNil) defer s.service.removeSecurityGroup(group1.Id) err = s.service.addSecurityGroup(group2) c.Assert(err, gc.IsNil) defer s.service.removeSecurityGroup(group2.Id) riGroup := neutron.RuleInfoV2{ ParentGroupId: group2.Id, Direction: "egress", } rule := neutron.SecurityGroupRuleV2{ Id: "1", ParentGroupId: group2.Id, Direction: "egress", } err = s.service.addSecurityGroupRule(rule.Id, riGroup) c.Assert(err, gc.IsNil) url := fmt.Sprintf("%s/%s", neutron.ApiSecurityGroupRulesV2, "1") resp, err := s.authRequest("DELETE", url, nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusNoContent) ok := s.service.hasSecurityGroupRule(group2.Id, rule.Id) c.Assert(ok, gc.Equals, false) } func (s *NeutronHTTPSuite) TestPostFloatingIPV2(c *gc.C) { // network 998 has External = true fip := neutron.FloatingIPV2{Id: "1", IP: "10.0.0.1", FloatingNetworkId: "998"} c.Assert(s.service.allFloatingIPs(), gc.HasLen, 0) var req struct { IP neutron.FloatingIPV2 `json:"floatingip"` } req.IP = fip resp, err := s.jsonRequest("POST", neutron.ApiFloatingIPsV2, req, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusCreated) var expected struct { IP neutron.FloatingIPV2 `json:"floatingip"` } assertJSON(c, resp, &expected) c.Assert(expected.IP, gc.DeepEquals, fip) err = s.service.removeFloatingIP(fip.Id) c.Assert(err, gc.IsNil) // network 999 has External = false req.IP.FloatingNetworkId = "999" resp, err = s.jsonRequest("POST", neutron.ApiFloatingIPsV2, req, nil) c.Assert(resp.StatusCode, gc.Equals, http.StatusNotFound) } func (s *NeutronHTTPSuite) TestGetFloatingIPs(c *gc.C) { c.Assert(s.service.allFloatingIPs(), gc.HasLen, 0) var expected struct { IPs []neutron.FloatingIPV2 `json:"floatingips"` } resp, err := s.authRequest("GET", neutron.ApiFloatingIPsV2, nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) c.Assert(expected.IPs, gc.HasLen, 0) fips := []neutron.FloatingIPV2{ {Id: "1", IP: "1.2.3.4"}, {Id: "2", IP: "4.3.2.1"}, } for _, fip := range fips { err := s.service.addFloatingIP(fip) defer s.service.removeFloatingIP(fip.Id) c.Assert(err, gc.IsNil) } resp, err = s.authRequest("GET", neutron.ApiFloatingIPsV2, nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) if expected.IPs[0].Id != fips[0].Id { expected.IPs[0], expected.IPs[1] = expected.IPs[1], expected.IPs[0] } c.Assert(expected.IPs, gc.DeepEquals, fips) var expectedIP struct { IP neutron.FloatingIPV2 `json:"floatingip"` } url := fmt.Sprintf("%s/%s", neutron.ApiFloatingIPsV2, "1") resp, err = s.authRequest("GET", url, nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expectedIP) c.Assert(expectedIP.IP, gc.DeepEquals, fips[0]) } func (s *NeutronHTTPSuite) TestDeleteFloatingIP(c *gc.C) { fip := neutron.FloatingIPV2{Id: "1", IP: "10.0.0.1"} err := s.service.addFloatingIP(fip) c.Assert(err, gc.IsNil) defer s.service.removeFloatingIP(fip.Id) url := fmt.Sprintf("%s/%s", neutron.ApiFloatingIPsV2, "1") resp, err := s.authRequest("DELETE", url, nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusNoContent) _, err = s.service.floatingIP(fip.Id) c.Assert(err, gc.NotNil) } func (s *NeutronHTTPSuite) TestGetNetworks(c *gc.C) { // There are always 4 networks networks, err := s.service.allNetworks(nil) c.Assert(err, gc.IsNil) c.Assert(networks, gc.HasLen, 4) var expected struct { Networks []neutron.NetworkV2 `json:"networks"` } resp, err := s.authRequest("GET", neutron.ApiNetworksV2, nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) c.Assert(expected.Networks, gc.HasLen, len(networks)) var expectedNetwork struct { Network neutron.NetworkV2 `json:"network"` } url := fmt.Sprintf("%s/%s", neutron.ApiNetworksV2, networks[0].Id) resp, err = s.authRequest("GET", url, nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expectedNetwork) c.Assert(expectedNetwork.Network, gc.DeepEquals, networks[0]) } func (s *NeutronHTTPSuite) TestGetSubnets(c *gc.C) { // There are always 3 subnets subnets := s.service.allSubnets() c.Assert(subnets, gc.HasLen, 3) var expected struct { Subnets []neutron.SubnetV2 `json:"subnets"` } resp, err := s.authRequest("GET", neutron.ApiSubnetsV2, nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) c.Assert(expected.Subnets, gc.HasLen, 3) var expectedSubnet struct { Subnet neutron.SubnetV2 `json:"subnet"` } url := fmt.Sprintf("%s/%s", neutron.ApiSubnetsV2, subnets[0].Id) resp, err = s.authRequest("GET", url, nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expectedSubnet) c.Assert(expectedSubnet.Subnet, gc.DeepEquals, subnets[0]) } func (s *NeutronHTTPSSuite) SetUpSuite(c *gc.C) { s.HTTPSuite.SetUpSuite(c) identityDouble := identityservice.NewUserPass() userInfo := identityDouble.AddUser("fred", "secret", "tenant", "default") s.token = userInfo.Token c.Assert(s.Server.URL[:8], gc.Equals, "https://") s.service = New(s.Server.URL, versionPath, userInfo.TenantId, region, identityDouble, nil) s.service.AddNeutronModel(neutronmodel.New()) } func (s *NeutronHTTPSSuite) TearDownSuite(c *gc.C) { s.HTTPSuite.TearDownSuite(c) } func (s *NeutronHTTPSSuite) SetUpTest(c *gc.C) { s.HTTPSuite.SetUpTest(c) s.service.SetupHTTP(s.Mux) } func (s *NeutronHTTPSSuite) TearDownTest(c *gc.C) { s.HTTPSuite.TearDownTest(c) } func (s *NeutronHTTPSSuite) TestHasHTTPSServiceURL(c *gc.C) { endpoints := s.service.Endpoints() c.Assert(endpoints[0].PublicURL[:8], gc.Equals, "https://") } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/neutronservice/service_test.go000066400000000000000000000444471414605405700307550ustar00rootroot00000000000000// Neutron double testing service - internal direct API tests package neutronservice import ( "fmt" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/neutron" "gopkg.in/goose.v1/testservices/neutronmodel" ) type NeutronSuite struct { service *Neutron } const ( versionPath = "v2.0" hostname = "http://example.com" region = "region" ) var _ = gc.Suite(&NeutronSuite{}) func (s *NeutronSuite) SetUpSuite(c *gc.C) { s.service = New(hostname, versionPath, "tenant", region, nil, nil) s.service.AddNeutronModel(neutronmodel.New()) } func (s *NeutronSuite) ensureNoGroup(c *gc.C, group neutron.SecurityGroupV2) { _, err := s.service.securityGroup(group.Id) c.Assert(err, gc.ErrorMatches, fmt.Sprintf("itemNotFound: No such security group %s", group.Id)) } func (s *NeutronSuite) ensureNoRule(c *gc.C, rule neutron.SecurityGroupRuleV2) { _, err := s.service.securityGroupRule(rule.Id) c.Assert(err, gc.ErrorMatches, fmt.Sprintf("itemNotFound: No such security group rule %s", rule.Id)) } func (s *NeutronSuite) ensureNoIP(c *gc.C, ip neutron.FloatingIPV2) { _, err := s.service.floatingIP(ip.Id) c.Assert(err, gc.ErrorMatches, fmt.Sprintf("itemNotFound: No such floating IP %q", ip.Id)) } func (s *NeutronSuite) ensureNoNetwork(c *gc.C, network neutron.NetworkV2) { _, err := s.service.network(network.Id) c.Assert(err, gc.ErrorMatches, fmt.Sprintf("itemNotFound: No such network %q", network.Id)) } func (s *NeutronSuite) ensureNoSubnet(c *gc.C, subnet neutron.SubnetV2) { _, err := s.service.subnet(subnet.Id) c.Assert(err, gc.ErrorMatches, fmt.Sprintf("itemNotFound: No such subnet %q", subnet.Id)) } func (s *NeutronSuite) createGroup(c *gc.C, group neutron.SecurityGroupV2) { s.ensureNoGroup(c, group) err := s.service.addSecurityGroup(group) c.Assert(err, gc.IsNil) } func (s *NeutronSuite) createIP(c *gc.C, ip neutron.FloatingIPV2) { s.ensureNoIP(c, ip) err := s.service.addFloatingIP(ip) c.Assert(err, gc.IsNil) } func (s *NeutronSuite) deleteGroup(c *gc.C, group neutron.SecurityGroupV2) { err := s.service.removeSecurityGroup(group.Id) c.Assert(err, gc.IsNil) s.ensureNoGroup(c, group) } func (s *NeutronSuite) deleteRule(c *gc.C, rule neutron.SecurityGroupRuleV2) { err := s.service.removeSecurityGroupRule(rule.Id) c.Assert(err, gc.IsNil) s.ensureNoRule(c, rule) } func (s *NeutronSuite) deleteIP(c *gc.C, ip neutron.FloatingIPV2) { err := s.service.removeFloatingIP(ip.Id) c.Assert(err, gc.IsNil) s.ensureNoIP(c, ip) } func (s *NeutronSuite) TestAddRemoveSecurityGroup(c *gc.C) { group := neutron.SecurityGroupV2{Id: "1"} s.createGroup(c, group) s.deleteGroup(c, group) } func (s *NeutronSuite) TestRemoveSecurityGroupTwiceFails(c *gc.C) { group := neutron.SecurityGroupV2{Id: "1", Name: "test"} s.createGroup(c, group) s.deleteGroup(c, group) err := s.service.removeSecurityGroup(group.Id) c.Assert(err, gc.ErrorMatches, "itemNotFound: No such security group 1") } func (s *NeutronSuite) TestAllSecurityGroups(c *gc.C) { groups := s.service.allSecurityGroups() // There is always a default security group. c.Assert(groups, gc.HasLen, 1) groups = []neutron.SecurityGroupV2{ { Id: "1", Name: "one", TenantId: s.service.TenantId, Rules: []neutron.SecurityGroupRuleV2{}, }, { Id: "2", Name: "two", TenantId: s.service.TenantId, Rules: []neutron.SecurityGroupRuleV2{}, }, } s.createGroup(c, groups[0]) defer s.deleteGroup(c, groups[0]) s.createGroup(c, groups[1]) defer s.deleteGroup(c, groups[1]) groups[0].Rules = defaultSecurityGroupRules(groups[0].Id, groups[0].TenantId) groups[1].Rules = defaultSecurityGroupRules(groups[1].Id, groups[1].TenantId) gr := s.service.allSecurityGroups() c.Assert(gr, gc.HasLen, len(groups)+1) checkGroupsInList(c, groups, gr) } func (s *NeutronSuite) TestGetSecurityGroup(c *gc.C) { group := neutron.SecurityGroupV2{ Id: "42", TenantId: s.service.TenantId, Name: "group", Description: "desc", Rules: []neutron.SecurityGroupRuleV2{}, } s.createGroup(c, group) group.Rules = defaultSecurityGroupRules(group.Id, group.TenantId) defer s.deleteGroup(c, group) gr, _ := s.service.securityGroup(group.Id) c.Assert(*gr, gc.DeepEquals, group) } func (s *NeutronSuite) TestGetSecurityGroupByName(c *gc.C) { group := neutron.SecurityGroupV2{ Id: "1", Name: "test", TenantId: s.service.TenantId, Rules: []neutron.SecurityGroupRuleV2{}, } s.ensureNoGroup(c, group) gr, err := s.service.securityGroupByName(group.Name) c.Assert(gr, gc.HasLen, 0) s.createGroup(c, group) defer s.deleteGroup(c, group) group.Rules = defaultSecurityGroupRules(group.Id, group.TenantId) gr, err = s.service.securityGroupByName(group.Name) c.Assert(err, gc.IsNil) c.Assert(gr, gc.HasLen, 1) c.Assert(gr[0], gc.DeepEquals, group) group2 := neutron.SecurityGroupV2{ Id: "2", Name: "test group", TenantId: s.service.TenantId, Rules: []neutron.SecurityGroupRuleV2{}, } s.ensureNoGroup(c, group2) gr2, err := s.service.securityGroupByName(group2.Name) c.Assert(gr2, gc.HasLen, 0) s.createGroup(c, group2) defer s.deleteGroup(c, group2) group2.Rules = defaultSecurityGroupRules(group2.Id, group2.TenantId) gr2, err = s.service.securityGroupByName(group2.Name) c.Assert(err, gc.IsNil) c.Assert(gr2, gc.HasLen, 1) c.Assert(gr2[0], gc.DeepEquals, group2) } func (s *NeutronSuite) TestAddHasRemoveSecurityGroupRule(c *gc.C) { group := neutron.SecurityGroupV2{Id: "1"} ri := neutron.RuleInfoV2{ParentGroupId: group.Id, Direction: "egress"} rule := neutron.SecurityGroupRuleV2{Id: "10", ParentGroupId: group.Id} s.ensureNoGroup(c, group) s.ensureNoRule(c, rule) ok := s.service.hasSecurityGroupRule(group.Id, rule.Id) c.Assert(ok, gc.Equals, false) s.createGroup(c, group) err := s.service.addSecurityGroupRule(rule.Id, ri) c.Assert(err, gc.IsNil) ok = s.service.hasSecurityGroupRule(group.Id, rule.Id) c.Assert(ok, gc.Equals, true) s.deleteGroup(c, group) ok = s.service.hasSecurityGroupRule("-1", rule.Id) c.Assert(ok, gc.Equals, true) ok = s.service.hasSecurityGroupRule(group.Id, rule.Id) c.Assert(ok, gc.Equals, false) s.deleteRule(c, rule) ok = s.service.hasSecurityGroupRule("-1", rule.Id) c.Assert(ok, gc.Equals, false) } func (s *NeutronSuite) TestAddGetIngressSecurityGroupRule(c *gc.C) { group := neutron.SecurityGroupV2{Id: "1"} s.createGroup(c, group) defer s.deleteGroup(c, group) ri := neutron.RuleInfoV2{ Direction: "ingress", PortRangeMax: 1234, PortRangeMin: 4321, IPProtocol: "tcp", ParentGroupId: group.Id, RemoteIPPrefix: "1.2.3.4/5", } rule := neutron.SecurityGroupRuleV2{ Id: "10", Direction: "ingress", PortRangeMax: &ri.PortRangeMax, PortRangeMin: &ri.PortRangeMin, IPProtocol: &ri.IPProtocol, ParentGroupId: group.Id, RemoteIPPrefix: "1.2.3.4/5", } s.ensureNoRule(c, rule) err := s.service.addSecurityGroupRule(rule.Id, ri) c.Assert(err, gc.IsNil) defer s.deleteRule(c, rule) ru, err := s.service.securityGroupRule(rule.Id) c.Assert(err, gc.IsNil) c.Assert(ru.Id, gc.Equals, rule.Id) c.Assert(ru.Direction, gc.Equals, rule.Direction) c.Assert(ru.ParentGroupId, gc.Equals, rule.ParentGroupId) c.Assert(*ru.PortRangeMax, gc.Equals, *rule.PortRangeMax) c.Assert(*ru.PortRangeMin, gc.Equals, *rule.PortRangeMin) c.Assert(*ru.IPProtocol, gc.Equals, *rule.IPProtocol) c.Assert(ru.RemoteIPPrefix, gc.Equals, rule.RemoteIPPrefix) } func (s *NeutronSuite) TestAddGetGroupSecurityGroupRule(c *gc.C) { srcGroup := neutron.SecurityGroupV2{Id: "1", Name: "source", TenantId: s.service.TenantId} tgtGroup := neutron.SecurityGroupV2{Id: "2", Name: "target", TenantId: s.service.TenantId} s.createGroup(c, srcGroup) defer s.deleteGroup(c, srcGroup) s.createGroup(c, tgtGroup) defer s.deleteGroup(c, tgtGroup) ri := neutron.RuleInfoV2{ Direction: "ingress", PortRangeMax: 1234, PortRangeMin: 4321, IPProtocol: "tcp", ParentGroupId: tgtGroup.Id, } rule := neutron.SecurityGroupRuleV2{ Id: "10", Direction: "ingress", ParentGroupId: tgtGroup.Id, PortRangeMax: &ri.PortRangeMax, PortRangeMin: &ri.PortRangeMin, IPProtocol: &ri.IPProtocol, } s.ensureNoRule(c, rule) err := s.service.addSecurityGroupRule(rule.Id, ri) c.Assert(err, gc.IsNil) defer s.deleteRule(c, rule) ru, err := s.service.securityGroupRule(rule.Id) c.Assert(err, gc.IsNil) c.Assert(ru.Id, gc.Equals, rule.Id) c.Assert(ru.ParentGroupId, gc.Equals, rule.ParentGroupId) c.Assert(*ru.PortRangeMax, gc.Equals, *rule.PortRangeMax) c.Assert(*ru.PortRangeMin, gc.Equals, *rule.PortRangeMin) c.Assert(*ru.IPProtocol, gc.Equals, *rule.IPProtocol) c.Assert(ru.Direction, gc.Equals, rule.Direction) } func (s *NeutronSuite) TestAddSecurityGroupRuleToParentTwiceFails(c *gc.C) { group := neutron.SecurityGroupV2{ Id: "1", Name: "TestAddSecurityGroupRuleToParentTwiceFails", } s.createGroup(c, group) defer s.deleteGroup(c, group) ri := neutron.RuleInfoV2{ParentGroupId: group.Id, Direction: "ingress"} rule := neutron.SecurityGroupRuleV2{Id: "10"} defer s.deleteRule(c, rule) err := s.service.addSecurityGroupRule(rule.Id, ri) c.Assert(err, gc.IsNil) err = s.service.addSecurityGroupRule(rule.Id, ri) c.Assert(err, gc.ErrorMatches, "conflictingRequest: Security group rule already exists. Group id is 1.") } func (s *NeutronSuite) TestAddSecurityGroupRuleWithInvalidParentFails(c *gc.C) { invalidGroup := neutron.SecurityGroupV2{Id: "1"} s.ensureNoGroup(c, invalidGroup) ri := neutron.RuleInfoV2{ParentGroupId: invalidGroup.Id, Direction: "egress"} rule := neutron.SecurityGroupRuleV2{Id: "10", Direction: "egress"} s.ensureNoRule(c, rule) err := s.service.addSecurityGroupRule(rule.Id, ri) c.Assert(err, gc.ErrorMatches, "itemNotFound: No such security group 1") } func (s *NeutronSuite) TestAddGroupSecurityGroupRuleWithInvalidDirectionFails(c *gc.C) { group := neutron.SecurityGroupV2{Id: "1"} s.createGroup(c, group) defer s.deleteGroup(c, group) invalidDirection := "42" ri := neutron.RuleInfoV2{ ParentGroupId: group.Id, Direction: invalidDirection, } rule := neutron.SecurityGroupRuleV2{Id: "10"} s.ensureNoRule(c, rule) err := s.service.addSecurityGroupRule(rule.Id, ri) c.Assert(err, gc.ErrorMatches, "badRequest: Invalid input for direction. Reason: 42 is not ingress or egress.") } func (s *NeutronSuite) TestAddSecurityGroupRuleUpdatesParent(c *gc.C) { group := neutron.SecurityGroupV2{ Id: "8", Name: "test", TenantId: s.service.TenantId, } s.createGroup(c, group) defer s.deleteGroup(c, group) ri := neutron.RuleInfoV2{ParentGroupId: group.Id, Direction: "egress"} rule := neutron.SecurityGroupRuleV2{ Id: "45", ParentGroupId: group.Id, Direction: "egress", TenantId: s.service.TenantId, EthernetType: "IPv4", } s.ensureNoRule(c, rule) err := s.service.addSecurityGroupRule(rule.Id, ri) c.Assert(err, gc.IsNil) defer s.deleteRule(c, rule) group.Rules = defaultSecurityGroupRules(group.Id, group.TenantId) group.Rules = append(group.Rules, rule) gr, err := s.service.securityGroup(group.Id) c.Assert(err, gc.IsNil) c.Assert(*gr, gc.DeepEquals, group) } func (s *NeutronSuite) TestRemoveSecurityGroupRuleTwiceFails(c *gc.C) { group := neutron.SecurityGroupV2{Id: "1"} s.createGroup(c, group) defer s.deleteGroup(c, group) ri := neutron.RuleInfoV2{ParentGroupId: group.Id, Direction: "egress"} rule := neutron.SecurityGroupRuleV2{Id: "10"} s.ensureNoRule(c, rule) err := s.service.addSecurityGroupRule(rule.Id, ri) c.Assert(err, gc.IsNil) s.deleteRule(c, rule) err = s.service.removeSecurityGroupRule(rule.Id) c.Assert(err, gc.ErrorMatches, "itemNotFound: No such security group rule 10") } func (s *NeutronSuite) TestAddHasRemoveFloatingIP(c *gc.C) { ip := neutron.FloatingIPV2{Id: "1", IP: "1.2.3.4"} s.ensureNoIP(c, ip) ok := s.service.hasFloatingIP(ip.IP) c.Assert(ok, gc.Equals, false) s.createIP(c, ip) ok = s.service.hasFloatingIP("invalid IP") c.Assert(ok, gc.Equals, false) ok = s.service.hasFloatingIP(ip.IP) c.Assert(ok, gc.Equals, true) s.deleteIP(c, ip) ok = s.service.hasFloatingIP(ip.IP) c.Assert(ok, gc.Equals, false) } func (s *NeutronSuite) TestAddFloatingIPTwiceFails(c *gc.C) { ip := neutron.FloatingIPV2{Id: "1"} s.createIP(c, ip) defer s.deleteIP(c, ip) err := s.service.addFloatingIP(ip) c.Assert(err, gc.ErrorMatches, "conflictingRequest: A floating IP with id 1 already exists") } func (s *NeutronSuite) TestRemoveFloatingIPTwiceFails(c *gc.C) { ip := neutron.FloatingIPV2{Id: "1"} s.createIP(c, ip) s.deleteIP(c, ip) err := s.service.removeFloatingIP(ip.Id) c.Assert(err, gc.ErrorMatches, "itemNotFound: No such floating IP \"1\"") } func (s *NeutronSuite) TestAllFloatingIPs(c *gc.C) { fips := s.service.allFloatingIPs() c.Assert(fips, gc.HasLen, 0) fips = []neutron.FloatingIPV2{ {Id: "1"}, {Id: "2"}, } s.createIP(c, fips[0]) defer s.deleteIP(c, fips[0]) s.createIP(c, fips[1]) defer s.deleteIP(c, fips[1]) ips := s.service.allFloatingIPs() c.Assert(ips, gc.HasLen, len(fips)) if ips[0].Id != fips[0].Id { ips[0], ips[1] = ips[1], ips[0] } c.Assert(ips, gc.DeepEquals, fips) } func (s *NeutronSuite) TestGetFloatingIP(c *gc.C) { fip := neutron.FloatingIPV2{ Id: "1", IP: "1.2.3.4", FloatingNetworkId: "sr1", FixedIP: "4.3.2.1", } s.createIP(c, fip) defer s.deleteIP(c, fip) ip, _ := s.service.floatingIP(fip.Id) c.Assert(*ip, gc.DeepEquals, fip) } func (s *NeutronSuite) TestGetFloatingIPByAddr(c *gc.C) { fip := neutron.FloatingIPV2{Id: "1", IP: "1.2.3.4"} s.ensureNoIP(c, fip) ip, err := s.service.floatingIPByAddr(fip.IP) c.Assert(err, gc.NotNil) s.createIP(c, fip) defer s.deleteIP(c, fip) ip, err = s.service.floatingIPByAddr(fip.IP) c.Assert(err, gc.IsNil) c.Assert(*ip, gc.DeepEquals, fip) _, err = s.service.floatingIPByAddr("invalid") c.Assert(err, gc.ErrorMatches, `itemNotFound: No such floating IP "invalid"`) } func (s *NeutronSuite) TestAllNetworksV2(c *gc.C) { networks, err := s.service.allNetworks(nil) c.Assert(err, gc.IsNil) newNets := []neutron.NetworkV2{ {Id: "75", Name: "ListNetwork75", External: true, SubnetIds: []string{}, TenantId: s.service.TenantId}, {Id: "42", Name: "ListNetwork42", External: true, SubnetIds: []string{}, TenantId: s.service.TenantId}, } err = s.service.addNetwork(newNets[0]) c.Assert(err, gc.IsNil) defer s.service.removeNetwork(newNets[0].Id) err = s.service.addNetwork(newNets[1]) c.Assert(err, gc.IsNil) defer s.service.removeNetwork(newNets[1].Id) newNets[0].TenantId = s.service.TenantId newNets[1].TenantId = s.service.TenantId networks = append(networks, newNets...) foundNetworks, err := s.service.allNetworks(nil) c.Assert(err, gc.IsNil) c.Assert(foundNetworks, gc.HasLen, len(networks)) for _, net := range networks { for _, newNet := range foundNetworks { if net.Id == newNet.Id { c.Assert(net, gc.DeepEquals, newNet) } } } } func (s *NeutronSuite) TestAllNetworksV2WithMultipleFilters(c *gc.C) { newNets := []neutron.NetworkV2{ {Id: "75", Name: "ListNetwork75", External: true, SubnetIds: []string{}, TenantId: s.service.TenantId}, {Id: "7", Name: "ListNetwork7", External: false, SubnetIds: []string{}, TenantId: s.service.TenantId}, {Id: "42", Name: "ListNetwork42", External: true, SubnetIds: []string{}, TenantId: s.service.TenantId}, } for _, net := range newNets { err := s.service.addNetwork(net) c.Assert(err, gc.IsNil) defer s.service.removeNetwork(net.Id) net.TenantId = s.service.TenantId } f := filter{ neutron.FilterRouterExternal: "true", neutron.FilterNetwork: "L.*4", } foundNetworks, err := s.service.allNetworks(f) c.Assert(err, gc.IsNil) c.Assert(foundNetworks, gc.HasLen, 1) c.Assert(foundNetworks[0], gc.DeepEquals, newNets[2]) } func (s *NeutronSuite) TestAllNetworksV2WithFilters(c *gc.C) { networks, err := s.service.allNetworks(nil) c.Assert(err, gc.IsNil) newNets := []neutron.NetworkV2{ {Id: "75", Name: "ListNetwork75", External: true, SubnetIds: []string{}, TenantId: s.service.TenantId}, {Id: "7", Name: "ListNetwork7", External: false, SubnetIds: []string{}, TenantId: s.service.TenantId}, {Id: "42", Name: "ListNetwork42", External: true, SubnetIds: []string{}, TenantId: s.service.TenantId}, } for _, net := range newNets { err := s.service.addNetwork(net) c.Assert(err, gc.IsNil) defer s.service.removeNetwork(net.Id) net.TenantId = s.service.TenantId } f := filter{ neutron.FilterRouterExternal: "true", } foundNetworks, err := s.service.allNetworks(f) c.Assert(err, gc.IsNil) c.Assert(foundNetworks, gc.HasLen, 4) for _, net := range networks { if net.External == true { for _, newNet := range foundNetworks { if net.Id == newNet.Id { c.Assert(net, gc.DeepEquals, newNet) } } } } f = filter{ neutron.FilterNetwork: "L.*7$", } foundNetworks, err = s.service.allNetworks(f) c.Assert(err, gc.IsNil) c.Assert(foundNetworks, gc.HasLen, 1) c.Assert(foundNetworks[0].Name, gc.Equals, "ListNetwork7") } func (s *NeutronSuite) TestGetNetworkV2(c *gc.C) { network := neutron.NetworkV2{ Id: "75", Name: "ListNetwork75", SubnetIds: []string{"32", "86"}, External: true, TenantId: s.service.TenantId, } s.ensureNoNetwork(c, network) s.service.addNetwork(network) defer s.service.removeNetwork(network.Id) net, _ := s.service.network(network.Id) c.Assert(*net, gc.DeepEquals, network) } func (s *NeutronSuite) TestAllSubnetsV2(c *gc.C) { subnets := s.service.allSubnets() newSubs := []neutron.SubnetV2{ {Id: "86", Name: "ListSubnet86", Cidr: "192.168.0.0/24"}, {Id: "92", Name: "ListSubnet92", Cidr: "192.169.0.0/24"}, } err := s.service.addSubnet(newSubs[0]) c.Assert(err, gc.IsNil) defer s.service.removeSubnet(newSubs[0].Id) err = s.service.addSubnet(newSubs[1]) c.Assert(err, gc.IsNil) defer s.service.removeSubnet(newSubs[1].Id) newSubs[0].TenantId = s.service.TenantId newSubs[1].TenantId = s.service.TenantId subnets = append(subnets, newSubs...) foundSubnets := s.service.allSubnets() c.Assert(foundSubnets, gc.HasLen, len(subnets)) for _, sub := range subnets { for _, newSub := range foundSubnets { if sub.Id == newSub.Id { c.Assert(sub, gc.DeepEquals, newSub) } } } } func (s *NeutronSuite) TestGetSubnetV2(c *gc.C) { subnet := neutron.SubnetV2{ Id: "82", Name: "ListSubnet82", TenantId: s.service.TenantId, } s.service.addSubnet(subnet) defer s.service.removeSubnet(subnet.Id) sub, _ := s.service.subnet(subnet.Id) c.Assert(*sub, gc.DeepEquals, subnet) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/neutronservice/setup_test.go000066400000000000000000000007361414605405700304460ustar00rootroot00000000000000package neutronservice import ( "testing" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/neutron" ) func Test(t *testing.T) { gc.TestingT(t) } // checkGroupsInList checks that every group in groups is in groupList. func checkGroupsInList(c *gc.C, groups []neutron.SecurityGroupV2, groupList []neutron.SecurityGroupV2) { for _, g := range groups { for _, gr := range groupList { if g.Id == gr.Id { c.Assert(g, gc.DeepEquals, gr) return } } c.Fail() } } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/novaservice/000077500000000000000000000000001414605405700251635ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/novaservice/service.go000066400000000000000000000713611414605405700271620ustar00rootroot00000000000000// Nova double testing service - internal direct API implementation package novaservice import ( "fmt" "net/url" "regexp" "sort" "strings" "gopkg.in/goose.v1/nova" "gopkg.in/goose.v1/testservices" "gopkg.in/goose.v1/testservices/identityservice" "gopkg.in/goose.v1/testservices/neutronmodel" ) var _ testservices.HttpService = (*Nova)(nil) var _ identityservice.ServiceProvider = (*Nova)(nil) // Nova implements a OpenStack Nova testing service and // contains the service double's internal state. type Nova struct { testservices.ServiceInstance neutronModel *neutronmodel.NeutronModel flavors map[string]nova.FlavorDetail servers map[string]nova.ServerDetail groups map[string]nova.SecurityGroup rules map[string]nova.SecurityGroupRule floatingIPs map[string]nova.FloatingIP networks map[string]nova.Network serverGroups map[string][]string serverIPs map[string][]string availabilityZones map[string]nova.AvailabilityZone serverIdToAttachedVolumes map[string][]nova.VolumeAttachment nextServerId int nextGroupId int nextRuleId int nextIPId int nextAttachmentId int useNeutronNetworking bool } func errorJSONEncode(err error) (int, string) { serverError, ok := err.(*testservices.ServerError) if !ok { serverError = testservices.NewInternalServerError(err.Error()) } return serverError.Code(), serverError.AsJSON() } // endpoint returns either a versioned or non-versioned service // endpoint URL from the given path. func (n *Nova) endpointURL(version bool, path string) string { ep := n.Scheme + "://" + n.Hostname if version { ep += n.VersionPath + "/" } ep += n.TenantId if path != "" { ep += "/" + strings.TrimLeft(path, "/") } return ep } func (n *Nova) Endpoints() []identityservice.Endpoint { ep := identityservice.Endpoint{ AdminURL: n.endpointURL(true, ""), InternalURL: n.endpointURL(true, ""), PublicURL: n.endpointURL(true, ""), Region: n.Region, } return []identityservice.Endpoint{ep} } func (n *Nova) V3Endpoints() []identityservice.V3Endpoint { url := n.endpointURL(true, "") return identityservice.NewV3Endpoints(url, url, url, n.RegionID) } // New creates an instance of the Nova object, given the parameters. func New(hostURL, versionPath, tenantId, region string, identityService, fallbackIdentity identityservice.IdentityService) *Nova { URL, err := url.Parse(hostURL) if err != nil { panic(err) } hostname := URL.Host if !strings.HasSuffix(hostname, "/") { hostname += "/" } // Real openstack instances have flavours "out of the box". So we add some here. defaultFlavors := []nova.FlavorDetail{ {Id: "1", Name: "m1.tiny", RAM: 512, VCPUs: 1}, {Id: "2", Name: "m1.small", RAM: 2048, VCPUs: 1}, {Id: "3", Name: "m1.medium", RAM: 4096, VCPUs: 2}, } // Real openstack instances have a default security group "out of the box". So we add it here. defaultSecurityGroups := []nova.SecurityGroup{ {Id: "999", Name: "default", Description: "default group"}, } novaService := &Nova{ flavors: make(map[string]nova.FlavorDetail), servers: make(map[string]nova.ServerDetail), groups: make(map[string]nova.SecurityGroup), rules: make(map[string]nova.SecurityGroupRule), floatingIPs: make(map[string]nova.FloatingIP), networks: make(map[string]nova.Network), serverGroups: make(map[string][]string), serverIPs: make(map[string][]string), availabilityZones: make(map[string]nova.AvailabilityZone), serverIdToAttachedVolumes: make(map[string][]nova.VolumeAttachment), useNeutronNetworking: false, ServiceInstance: testservices.ServiceInstance{ IdentityService: identityService, FallbackIdentityService: fallbackIdentity, Scheme: URL.Scheme, Hostname: hostname, VersionPath: versionPath, TenantId: tenantId, Region: region, }, } if identityService != nil { identityService.RegisterServiceProvider("nova", "compute", novaService) } for i, flavor := range defaultFlavors { novaService.buildFlavorLinks(&flavor) defaultFlavors[i] = flavor err := novaService.addFlavor(flavor) if err != nil { panic(err) } } for _, group := range defaultSecurityGroups { err := novaService.addSecurityGroup(group) if err != nil { panic(err) } } // Add a sample default network var netId = "1" novaService.networks[netId] = nova.Network{ Id: netId, Label: "net", Cidr: "10.0.0.0/24", } return novaService } func (n *Nova) Stop() { // noop } // AddNeutronModel setups up the test double to use Neutron networking // which requires shared data between the nova and neutron test doubles. func (n *Nova) AddNeutronModel(neutronModel *neutronmodel.NeutronModel) { n.neutronModel = neutronModel n.useNeutronNetworking = true } // SetAvailabilityZones sets the availability zones for setting // availability zones. // // Note: this is implemented as a public method rather than as // an HTTP API for two reasons: availability zones are created // indirectly via "host aggregates", which are a cloud-provider // concept that we have not implemented, and because we want to // be able to synthesize zone state changes. func (n *Nova) SetAvailabilityZones(zones ...nova.AvailabilityZone) { n.availabilityZones = make(map[string]nova.AvailabilityZone) for _, z := range zones { n.availabilityZones[z.Name] = z } } // buildFlavorLinks populates the Links field of the passed // FlavorDetail as needed by OpenStack HTTP API. Call this // before addFlavor(). func (n *Nova) buildFlavorLinks(flavor *nova.FlavorDetail) { url := "/flavors/" + flavor.Id flavor.Links = []nova.Link{ {Href: n.endpointURL(true, url), Rel: "self"}, {Href: n.endpointURL(false, url), Rel: "bookmark"}, } } // addFlavor creates a new flavor. func (n *Nova) addFlavor(flavor nova.FlavorDetail) error { if err := n.ProcessFunctionHook(n, flavor); err != nil { return err } if _, err := n.flavor(flavor.Id); err == nil { return testservices.NewAddFlavorError(flavor.Id) } n.flavors[flavor.Id] = flavor return nil } // flavor retrieves an existing flavor by ID. func (n *Nova) flavor(flavorId string) (*nova.FlavorDetail, error) { if err := n.ProcessFunctionHook(n, flavorId); err != nil { return nil, err } flavor, ok := n.flavors[flavorId] if !ok { return nil, testservices.NewNoSuchFlavorError(flavorId) } return &flavor, nil } // flavorAsEntity returns the stored FlavorDetail as Entity. func (n *Nova) flavorAsEntity(flavorId string) (*nova.Entity, error) { if err := n.ProcessFunctionHook(n, flavorId); err != nil { return nil, err } flavor, err := n.flavor(flavorId) if err != nil { return nil, err } return &nova.Entity{ Id: flavor.Id, Name: flavor.Name, Links: flavor.Links, }, nil } // allFlavors returns a list of all existing flavors. func (n *Nova) allFlavors() []nova.FlavorDetail { var flavors []nova.FlavorDetail for _, flavor := range n.flavors { flavors = append(flavors, flavor) } return flavors } // allFlavorsAsEntities returns all flavors as Entity structs. func (n *Nova) allFlavorsAsEntities() []nova.Entity { var entities []nova.Entity for _, flavor := range n.flavors { entities = append(entities, nova.Entity{ Id: flavor.Id, Name: flavor.Name, Links: flavor.Links, }) } return entities } // removeFlavor deletes an existing flavor. func (n *Nova) removeFlavor(flavorId string) error { if err := n.ProcessFunctionHook(n, flavorId); err != nil { return err } if _, err := n.flavor(flavorId); err != nil { return err } delete(n.flavors, flavorId) return nil } // buildServerLinks populates the Links field of the passed // ServerDetail as needed by OpenStack HTTP API. Call this // before addServer(). func (n *Nova) buildServerLinks(server *nova.ServerDetail) { url := "/servers/" + server.Id server.Links = []nova.Link{ {Href: n.endpointURL(true, url), Rel: "self"}, {Href: n.endpointURL(false, url), Rel: "bookmark"}, } } // addServer creates a new server. func (n *Nova) addServer(server nova.ServerDetail) error { if err := n.ProcessFunctionHook(n, &server); err != nil { return err } if _, err := n.server(server.Id); err == nil { return testservices.NewServerAlreadyExistsError(server.Id) } n.servers[server.Id] = server return nil } // updateServerName creates a new server. func (n *Nova) updateServerName(serverId, name string) error { if err := n.ProcessFunctionHook(n, serverId); err != nil { return err } server, err := n.server(serverId) if err != nil { return testservices.NewServerByIDNotFoundError(serverId) } server.Name = name n.servers[serverId] = *server return nil } // server retrieves an existing server by ID. func (n *Nova) server(serverId string) (*nova.ServerDetail, error) { if err := n.ProcessFunctionHook(n, serverId); err != nil { return nil, err } server, ok := n.servers[serverId] if !ok { return nil, testservices.NewServerByIDNotFoundError(serverId) } return &server, nil } // serverByName retrieves the first existing server with the given name. func (n *Nova) serverByName(name string) (*nova.ServerDetail, error) { if err := n.ProcessFunctionHook(n, name); err != nil { return nil, err } for _, server := range n.servers { if server.Name == name { return &server, nil } } return nil, testservices.NewServerByNameNotFoundError(name) } // serverAsEntity returns the stored ServerDetail as Entity. func (n *Nova) serverAsEntity(serverId string) (*nova.Entity, error) { if err := n.ProcessFunctionHook(n, serverId); err != nil { return nil, err } server, err := n.server(serverId) if err != nil { return nil, err } return &nova.Entity{ Id: server.Id, UUID: server.UUID, Name: server.Name, Links: server.Links, }, nil } // filter is used internally by matchServers. type filter map[string]string // matchServers returns a list of matching servers, after applying the // given filter. Each separate filter is combined with a logical AND. // Each filter can have only one value. A nil filter matches all servers. // // This is tested to match OpenStack behavior. Regular expression // matching is supported for FilterServer only, and the supported // syntax is limited to whatever DB backend is used (see SQL // REGEXP/RLIKE). // // Example: // // f := filter{ // nova.FilterStatus: nova.StatusActive, // nova.FilterServer: `foo.*`, // } // // This will match all servers with status "ACTIVE", and names starting // with "foo". func (n *Nova) matchServers(f filter) ([]nova.ServerDetail, error) { if err := n.ProcessFunctionHook(n, f); err != nil { return nil, err } var servers []nova.ServerDetail for _, server := range n.servers { servers = append(servers, server) } if len(f) == 0 { return servers, nil // empty filter matches everything } if status := f[nova.FilterStatus]; status != "" { matched := []nova.ServerDetail{} for _, server := range servers { if server.Status == status { matched = append(matched, server) } } if len(matched) == 0 { // no match, so no need to look further return nil, nil } servers = matched } if nameRex := f[nova.FilterServer]; nameRex != "" { matched := []nova.ServerDetail{} rex, err := regexp.Compile(nameRex) if err != nil { return nil, err } for _, server := range servers { if rex.MatchString(server.Name) { matched = append(matched, server) } } if len(matched) == 0 { // no match, here so ignore other results return nil, nil } servers = matched } return servers, nil // TODO(dimitern) - 2013-02-11 bug=1121690 // implement FilterFlavor, FilterImage, FilterMarker, FilterLimit and FilterChangesSince } // allServers returns a list of all existing servers. // Filtering is supported, see filter type for more info. func (n *Nova) allServers(f filter) ([]nova.ServerDetail, error) { return n.matchServers(f) } // allServersAsEntities returns all servers as Entity structs. // Filtering is supported, see filter type for more info. func (n *Nova) allServersAsEntities(f filter) ([]nova.Entity, error) { var entities []nova.Entity servers, err := n.matchServers(f) if err != nil { return nil, err } for _, server := range servers { entities = append(entities, nova.Entity{ Id: server.Id, UUID: server.UUID, Name: server.Name, Links: server.Links, }) } return entities, nil } // removeServer deletes an existing server. func (n *Nova) removeServer(serverId string) error { if err := n.ProcessFunctionHook(n, serverId); err != nil { return err } if _, err := n.server(serverId); err != nil { return err } delete(n.servers, serverId) return nil } func (n *Nova) updateSecurityGroup(group nova.SecurityGroup) error { if err := n.ProcessFunctionHook(n, group); err != nil { return err } if n.useNeutronNetworking { return n.neutronModel.UpdateNovaSecurityGroup(group) } existingGroup, err := n.securityGroup(group.Id) if err != nil { return testservices.NewSecurityGroupByIDNotFoundError(group.Id) } existingGroup.Name = group.Name existingGroup.Description = group.Description n.groups[group.Id] = *existingGroup return nil } // addSecurityGroup creates a new security group. func (n *Nova) addSecurityGroup(group nova.SecurityGroup) error { if err := n.ProcessFunctionHook(n, group); err != nil { return err } if n.useNeutronNetworking { return n.neutronModel.AddNovaSecurityGroup(group) } if _, err := n.securityGroup(group.Id); err == nil { return testservices.NewSecurityGroupAlreadyExistsError(group.Id) } group.TenantId = n.TenantId if group.Rules == nil { group.Rules = []nova.SecurityGroupRule{} } n.groups[group.Id] = group return nil } // securityGroup retrieves an existing group by ID. func (n *Nova) securityGroup(groupId string) (*nova.SecurityGroup, error) { if err := n.ProcessFunctionHook(n, groupId); err != nil { return nil, err } if n.useNeutronNetworking { return n.neutronModel.NovaSecurityGroup(groupId) } group, ok := n.groups[groupId] if !ok { return nil, testservices.NewSecurityGroupByIDNotFoundError(groupId) } return &group, nil } // securityGroupByName retrieves an existing named group. func (n *Nova) securityGroupByName(groupName string) (*nova.SecurityGroup, error) { if err := n.ProcessFunctionHook(n, groupName); err != nil { return nil, err } if n.useNeutronNetworking { return n.neutronModel.NovaSecurityGroupByName(groupName) } for _, group := range n.groups { if group.Name == groupName { return &group, nil } } return nil, testservices.NewSecurityGroupByNameNotFoundError(groupName) } // allSecurityGroups returns a list of all existing groups. func (n *Nova) allSecurityGroups() []nova.SecurityGroup { var groups []nova.SecurityGroup if n.useNeutronNetworking { return n.neutronModel.AllNovaSecurityGroups() } for _, group := range n.groups { groups = append(groups, group) } return groups } // removeSecurityGroup deletes an existing group. func (n *Nova) removeSecurityGroup(groupId string) error { if err := n.ProcessFunctionHook(n, groupId); err != nil { return err } if n.useNeutronNetworking { return n.neutronModel.RemoveSecurityGroup(groupId) } if _, err := n.securityGroup(groupId); err != nil { return err } delete(n.groups, groupId) return nil } // addSecurityGroupRule creates a new rule in an existing group. // This can be either an ingress or a group rule (see the notes // about nova.RuleInfo). func (n *Nova) addSecurityGroupRule(ruleId string, rule nova.RuleInfo) error { if err := n.ProcessFunctionHook(n, ruleId, rule); err != nil { return err } if _, err := n.securityGroupRule(ruleId); err == nil { return testservices.NewSecurityGroupRuleAlreadyExistsError(ruleId) } group, err := n.securityGroup(rule.ParentGroupId) if err != nil { return err } for _, ru := range group.Rules { if ru.Id == ruleId { return testservices.NewCannotAddTwiceRuleToGroupError(ru.Id, group.Id) } } var zeroSecurityGroupRef nova.SecurityGroupRef newrule := nova.SecurityGroupRule{ ParentGroupId: rule.ParentGroupId, Id: ruleId, Group: zeroSecurityGroupRef, } if rule.GroupId != nil { sourceGroup, err := n.securityGroup(*rule.GroupId) if err != nil { return testservices.NewUnknownSecurityGroupError(*rule.GroupId) } newrule.Group = nova.SecurityGroupRef{ TenantId: sourceGroup.TenantId, Name: sourceGroup.Name, } } else if rule.Cidr == "" { // http://pad.lv/1226996 // It seems that if you don't supply Cidr or GroupId then // Openstack treats the Cidr as 0.0.0.0/0 // However, since that is not clearly specified we just panic() // because we don't want to rely on that behavior panic(fmt.Sprintf("Neither Cidr nor GroupId are set for this SecurityGroup Rule: %v", rule)) } if rule.FromPort != 0 { newrule.FromPort = &rule.FromPort } if rule.ToPort != 0 { newrule.ToPort = &rule.ToPort } if rule.IPProtocol != "" { newrule.IPProtocol = &rule.IPProtocol } if rule.Cidr != "" { newrule.IPRange = make(map[string]string) newrule.IPRange["cidr"] = rule.Cidr } group.Rules = append(group.Rules, newrule) n.groups[group.Id] = *group n.rules[newrule.Id] = newrule return nil } // hasSecurityGroupRule returns whether the given group contains the given rule, // or (when groupId="-1") whether the given rule exists. func (n *Nova) hasSecurityGroupRule(groupId, ruleId string) bool { rule, ok := n.rules[ruleId] _, err := n.securityGroup(groupId) return ok && (groupId == "-1" || (err == nil && rule.ParentGroupId == groupId)) } // securityGroupRule retrieves an existing rule by ID. func (n *Nova) securityGroupRule(ruleId string) (*nova.SecurityGroupRule, error) { if err := n.ProcessFunctionHook(n, ruleId); err != nil { return nil, err } rule, ok := n.rules[ruleId] if !ok { return nil, testservices.NewSecurityGroupRuleNotFoundError(ruleId) } return &rule, nil } // removeSecurityGroupRule deletes an existing rule from its group. func (n *Nova) removeSecurityGroupRule(ruleId string) error { if err := n.ProcessFunctionHook(n, ruleId); err != nil { return err } rule, err := n.securityGroupRule(ruleId) if err != nil { return err } if group, err := n.securityGroup(rule.ParentGroupId); err == nil { idx := -1 for ri, ru := range group.Rules { if ru.Id == ruleId { idx = ri break } } if idx != -1 { group.Rules = append(group.Rules[:idx], group.Rules[idx+1:]...) n.groups[group.Id] = *group } // Silently ignore missing rules... } // ...or groups delete(n.rules, ruleId) return nil } // addServerSecurityGroup attaches an existing server to a group. func (n *Nova) addServerSecurityGroup(serverId string, groupId string) error { if err := n.ProcessFunctionHook(n, serverId, groupId); err != nil { return err } if n.useNeutronNetworking { if _, err := n.neutronModel.NovaSecurityGroup(groupId); err != nil { return err } } else { if _, err := n.securityGroup(groupId); err != nil { return err } } if _, err := n.server(serverId); err != nil { return err } groups, ok := n.serverGroups[serverId] if ok { for _, gid := range groups { if gid == groupId { return testservices.NewServerBelongsToGroupError(serverId, groupId) } } } groups = append(groups, groupId) n.serverGroups[serverId] = groups return nil } // hasServerSecurityGroup returns whether the given server belongs to the group. func (n *Nova) hasServerSecurityGroup(serverId string, groupId string) bool { if n.useNeutronNetworking { if _, err := n.neutronModel.NovaSecurityGroup(groupId); err != nil { return false } } else { if _, err := n.securityGroup(groupId); err != nil { return false } } if _, err := n.server(serverId); err != nil { return false } groups, ok := n.serverGroups[serverId] if !ok { return false } for _, gid := range groups { if gid == groupId { return true } } return false } // allServerSecurityGroups returns all security groups attached to the // given server. func (n *Nova) allServerSecurityGroups(serverId string) []nova.SecurityGroup { var groups []nova.SecurityGroup for _, gid := range n.serverGroups[serverId] { group, err := n.securityGroup(gid) if err != nil { return nil } groups = append(groups, *group) } return groups } // removeServerSecurityGroup detaches an existing server from a group. func (n *Nova) removeServerSecurityGroup(serverId string, groupId string) error { if err := n.ProcessFunctionHook(n, serverId, groupId); err != nil { return err } if n.useNeutronNetworking { if _, err := n.neutronModel.NovaSecurityGroup(groupId); err != nil { return err } } else { if _, err := n.securityGroup(groupId); err != nil { return err } } if _, err := n.server(serverId); err != nil { return err } groups, ok := n.serverGroups[serverId] if !ok { return testservices.NewServerDoesNotBelongToGroupsError(serverId) } idx := -1 for gi, gid := range groups { if gid == groupId { idx = gi break } } if idx == -1 { return testservices.NewServerDoesNotBelongToGroupError(serverId, groupId) } groups = append(groups[:idx], groups[idx+1:]...) n.serverGroups[serverId] = groups return nil } // addFloatingIP creates a new floating IP address in the pool. func (n *Nova) addFloatingIP(ip nova.FloatingIP) error { if err := n.ProcessFunctionHook(n, ip); err != nil { return err } if n.useNeutronNetworking { return n.neutronModel.AddNovaFloatingIP(ip) } if _, err := n.floatingIP(ip.Id); err == nil { return testservices.NewFloatingIPExistsError(ip.Id) } n.floatingIPs[ip.Id] = ip return nil } // hasFloatingIP returns whether the given floating IP address exists. func (n *Nova) hasFloatingIP(address string) bool { if n.useNeutronNetworking { return n.neutronModel.HasFloatingIP(address) } if len(n.floatingIPs) == 0 { return false } for _, fip := range n.floatingIPs { if fip.IP == address { return true } } return false } // floatingIP retrieves the floating IP by ID. func (n *Nova) floatingIP(ipId string) (*nova.FloatingIP, error) { if err := n.ProcessFunctionHook(n, ipId); err != nil { return nil, err } if n.useNeutronNetworking { return n.neutronModel.NovaFloatingIP(ipId) } ip, ok := n.floatingIPs[ipId] if !ok { return nil, testservices.NewFloatingIPNotFoundError(ipId) } return &ip, nil } // floatingIPByAddr retrieves the floating IP by address. func (n *Nova) floatingIPByAddr(address string) (*nova.FloatingIP, error) { if err := n.ProcessFunctionHook(n, address); err != nil { return nil, err } if n.useNeutronNetworking { return n.neutronModel.NovaFloatingIPByAddr(address) } for _, fip := range n.floatingIPs { if fip.IP == address { return &fip, nil } } return nil, testservices.NewFloatingIPNotFoundError(address) } // allFloatingIPs returns a list of all created floating IPs. func (n *Nova) allFloatingIPs() []nova.FloatingIP { if n.useNeutronNetworking { return n.neutronModel.AllNovaFloatingIPs() } var fips []nova.FloatingIP for _, fip := range n.floatingIPs { fips = append(fips, fip) } return fips } // removeFloatingIP deletes an existing floating IP by ID. func (n *Nova) removeFloatingIP(ipId string) error { if err := n.ProcessFunctionHook(n, ipId); err != nil { return err } if n.useNeutronNetworking { return n.neutronModel.RemoveFloatingIP(ipId) } if _, err := n.floatingIP(ipId); err != nil { return err } delete(n.floatingIPs, ipId) return nil } // addServerFloatingIP attaches an existing floating IP to a server. func (n *Nova) addServerFloatingIP(serverId string, ipId string) error { if err := n.ProcessFunctionHook(n, serverId, ipId); err != nil { return err } if _, err := n.server(serverId); err != nil { return err } fixedIP := "4.3.2.1" // not important really, unused var fip *nova.FloatingIP var err error if n.useNeutronNetworking { fip, err = n.neutronModel.NovaFloatingIP(ipId) if err != nil { return err } fip.FixedIP = &fixedIP if err := n.neutronModel.UpdateNovaFloatingIP(fip); err != nil { return err } } else { fip, err = n.floatingIP(ipId) if err != nil { return err } else { fip.FixedIP = &fixedIP fip.InstanceId = &serverId n.floatingIPs[ipId] = *fip } } fips, ok := n.serverIPs[serverId] if ok { for _, fipId := range fips { if fipId == ipId { return testservices.NewServerHasFloatingIPError(serverId, ipId) } } } fips = append(fips, ipId) n.serverIPs[serverId] = fips if err := n.addFloatingIPToServerAddresses(serverId, fip.IP); err != nil { return err } return nil } // addFloatingIPToServerAddresses adds a floating ip address to the servers list // of Addresses to facilitate juju openstack provider tests. func (n *Nova) addFloatingIPToServerAddresses(serverId, address string) error { server, err := n.server(serverId) if err != nil { return err } newAddresses := server.Addresses["private"] if strings.Contains(address, ":") { newAddresses = append(newAddresses, nova.IPAddress{6, address, "floating"}) } else { newAddresses = append(newAddresses, nova.IPAddress{4, address, "floating"}) } server.Addresses["private"] = newAddresses n.servers[serverId] = *server return nil } // hasServerFloatingIP verifies the given floating IP belongs to a server. func (n *Nova) hasServerFloatingIP(serverId, address string) bool { if _, err := n.server(serverId); err != nil { return false } var fip *nova.FloatingIP var err error if n.useNeutronNetworking { fip, err = n.neutronModel.NovaFloatingIPByAddr(address) } else { fip, err = n.floatingIPByAddr(address) } if err != nil { return false } fips, ok := n.serverIPs[serverId] if !ok { return false } for _, fipId := range fips { if fipId == fip.Id { return true } } return false } // removeFloatingIPFromServerAddresses removes a floating ip address from the // servers list of Addresses to facilitate juju openstack provider tests. func (n *Nova) removeFloatingIPFromServerAddresses(serverId, address string) error { server, err := n.server(serverId) if err != nil { return err } serverAddresses := []nova.IPAddress{} for _, serverAddress := range server.Addresses["private"] { if serverAddress.Address != address { serverAddresses = append(serverAddresses, serverAddress) } } if len(serverAddresses) != 0 { server.Addresses["private"] = serverAddresses } else { server.Addresses["private"] = []nova.IPAddress{} } n.servers[serverId] = *server return nil } // removeServerFloatingIP deletes an attached floating IP from a server. func (n *Nova) removeServerFloatingIP(serverId string, ipId string) error { if err := n.ProcessFunctionHook(n, serverId); err != nil { return err } if _, err := n.server(serverId); err != nil { return err } var fip *nova.FloatingIP var err error if n.useNeutronNetworking { fip, err = n.neutronModel.NovaFloatingIP(ipId) if err != nil { return err } fip.FixedIP = nil if err = n.neutronModel.UpdateNovaFloatingIP(fip); err != nil { return err } } else { if fip, err = n.floatingIP(ipId); err != nil { return err } else { fip.FixedIP = nil fip.InstanceId = nil n.floatingIPs[ipId] = *fip } } if err := n.removeFloatingIPFromServerAddresses(serverId, fip.IP); err != nil { return err } fips, ok := n.serverIPs[serverId] if !ok { return testservices.NewNoFloatingIPsToRemoveError(serverId) } idx := -1 for fi, fipId := range fips { if fipId == ipId { idx = fi break } } if idx == -1 { return testservices.NewNoFloatingIPsError(serverId, ipId) } fips = append(fips[:idx], fips[idx+1:]...) n.serverIPs[serverId] = fips return nil } // allNetworks returns a list of all existing networks. func (n *Nova) allNetworks() (networks []nova.Network) { if n.useNeutronNetworking { return n.neutronModel.AllNovaNetworks() } else { for _, net := range n.networks { networks = append(networks, net) } return networks } } // allAvailabilityZones returns a list of all existing availability zones, // sorted by name. func (n *Nova) allAvailabilityZones() (zones []nova.AvailabilityZone) { for _, zone := range n.availabilityZones { zones = append(zones, zone) } sort.Sort(azByName(zones)) return zones } type azByName []nova.AvailabilityZone func (a azByName) Len() int { return len(a) } func (a azByName) Less(i, j int) bool { return a[i].Name < a[j].Name } func (a azByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } // setServerMetadata sets metadata on a server. func (n *Nova) setServerMetadata(serverId string, metadata map[string]string) error { if err := n.ProcessFunctionHook(n, serverId, metadata); err != nil { return err } server, err := n.server(serverId) if err != nil { return err } if server.Metadata == nil { server.Metadata = make(map[string]string) } for k, v := range metadata { server.Metadata[k] = v } n.servers[serverId] = *server return nil } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/novaservice/service_http.go000066400000000000000000001075241414605405700302220ustar00rootroot00000000000000// Nova double testing service - HTTP API implementation package novaservice import ( "crypto/rand" "encoding/json" "fmt" "io" "io/ioutil" "net/http" "path" "regexp" "strconv" "strings" "time" "gopkg.in/goose.v1/errors" "gopkg.in/goose.v1/nova" "gopkg.in/goose.v1/testservices" "gopkg.in/goose.v1/testservices/identityservice" ) const authToken = "X-Auth-Token" // errorResponse defines a single HTTP error response. type errorResponse struct { code int body string contentType string errorText string headers map[string]string nova *Nova } var ( regexpVolumeAttachmentDevice = regexp.MustCompile("(^/dev/x{0,1}[a-z]{0,1}d{0,1})([a-z]+)[0-9]*$") ) // verbatim real Nova responses (as errors). var ( errUnauthorized = &errorResponse{ http.StatusUnauthorized, `401 Unauthorized This server could not verify that you are authorized to access the ` + `document you requested. Either you supplied the wrong ` + `credentials (e.g., bad password), or your browser does ` + `not understand how to supply the credentials required. Authentication required `, "text/plain; charset=UTF-8", "unauthorized request", nil, nil, } errForbidden = &errorResponse{ http.StatusForbidden, `{"forbidden": {"message": "Policy doesn't allow compute_extension:` + `flavormanage to be performed.", "code": 403}}`, "application/json; charset=UTF-8", "forbidden flavors request", nil, nil, } errBadRequest = &errorResponse{ http.StatusBadRequest, `{"badRequest": {"message": "Malformed request url", "code": 400}}`, "application/json; charset=UTF-8", "bad request base path or URL", nil, nil, } errBadRequest2 = &errorResponse{ http.StatusBadRequest, `{"badRequest": {"message": "The server could not comply with the ` + `request since it is either malformed or otherwise incorrect.", "code": 400}}`, "application/json; charset=UTF-8", "bad request URL", nil, nil, } errBadRequest3 = &errorResponse{ http.StatusBadRequest, `{"badRequest": {"message": "Malformed request body", "code": 400}}`, "application/json; charset=UTF-8", "bad request body", nil, nil, } errBadRequestDuplicateValue = &errorResponse{ http.StatusBadRequest, `{"badRequest": {"message": "entity already exists", "code": 400}}`, "application/json; charset=UTF-8", "duplicate value", nil, nil, } errBadRequestSrvName = &errorResponse{ http.StatusBadRequest, `{"badRequest": {"message": "Server name is not defined", "code": 400}}`, "application/json; charset=UTF-8", "bad request - missing server name", nil, nil, } errBadRequestSrvFlavor = &errorResponse{ http.StatusBadRequest, `{"badRequest": {"message": "Missing flavorRef attribute", "code": 400}}`, "application/json; charset=UTF-8", "bad request - missing flavorRef", nil, nil, } errBadRequestSrvImage = &errorResponse{ http.StatusBadRequest, `{"badRequest": {"message": "Missing imageRef attribute", "code": 400}}`, "application/json; charset=UTF-8", "bad request - missing imageRef", nil, nil, } errNotFound = &errorResponse{ http.StatusNotFound, `404 Not Found The resource could not be found. `, "text/plain; charset=UTF-8", "resource not found", nil, nil, } errNotFoundJSON = &errorResponse{ http.StatusNotFound, `{"itemNotFound": {"message": "The resource could not be found.", "code": 404}}`, "application/json; charset=UTF-8", "resource not found", nil, nil, } errNotFoundJSONSG = &errorResponse{ http.StatusNotFound, `{"itemNotFound": {"message": "Security group $ID$ not found.", "code": 404}}`, "application/json; charset=UTF-8", "", nil, nil, } errNotFoundJSONSGR = &errorResponse{ http.StatusNotFound, `{"itemNotFound": {"message": "Rule ($ID$) not found.", "code": 404}}`, "application/json; charset=UTF-8", "security rule not found", nil, nil, } errMultipleChoices = &errorResponse{ http.StatusMultipleChoices, `{"choices": [{"status": "CURRENT", "media-types": [{"base": ` + `"application/xml", "type": "application/vnd.openstack.compute+` + `xml;version=2"}, {"base": "application/json", "type": "application/` + `vnd.openstack.compute+json;version=2"}], "id": "v2.0", "links": ` + `[{"href": "$ENDPOINT$$URL$", "rel": "self"}]}]}`, "application/json", "multiple URL redirection choices", nil, nil, } errNoVersion = &errorResponse{ http.StatusOK, `{"versions": [` + `{"id": "v2.0", "links": [{"href": "v2", "rel": "self"}], "status": "SUPPORTED", "updated": "2011-01-21T11:33:21Z"}]}`, "application/json", "no version specified in URL", nil, nil, } errVersionsLinks = &errorResponse{ http.StatusOK, `{"version": {"status": "CURRENT", "updated": "2011-01-21T11` + `:33:21Z", "media-types": [{"base": "application/xml", "type": ` + `"application/vnd.openstack.compute+xml;version=2"}, {"base": ` + `"application/json", "type": "application/vnd.openstack.compute` + `+json;version=2"}], "id": "v2.0", "links": [{"href": "$ENDPOINT$"` + `, "rel": "self"}, {"href": "http://docs.openstack.org/api/openstack` + `-compute/1.1/os-compute-devguide-1.1.pdf", "type": "application/pdf` + `", "rel": "describedby"}, {"href": "http://docs.openstack.org/api/` + `openstack-compute/1.1/wadl/os-compute-1.1.wadl", "type": ` + `"application/vnd.sun.wadl+xml", "rel": "describedby"}]}}`, "application/json", "version missing from URL", nil, nil, } errNotImplemented = &errorResponse{ http.StatusNotImplemented, "501 Not Implemented", "text/plain; charset=UTF-8", "not implemented", nil, nil, } errNoGroupId = &errorResponse{ errorText: "no security group id given", } errRateLimitExceeded = &errorResponse{ http.StatusRequestEntityTooLarge, "", "text/plain; charset=UTF-8", "too many requests", // RFC says that Retry-After should be an int, but we don't want to wait an entire second during the test suite. map[string]string{"Retry-After": "0.001"}, nil, } errNoMoreFloatingIPs = &errorResponse{ http.StatusNotFound, "Zero floating ips available.", "text/plain; charset=UTF-8", "zero floating ips available", nil, nil, } errIPLimitExceeded = &errorResponse{ http.StatusRequestEntityTooLarge, "Maximum number of floating ips exceeded.", "text/plain; charset=UTF-8", "maximum number of floating ips exceeded", nil, nil, } ) func (e *errorResponse) Error() string { return e.errorText } // requestBody returns the body for the error response, replacing // $ENDPOINT$, $URL$, $ID$, and $ERROR$ in e.body with the values from // the request. func (e *errorResponse) requestBody(r *http.Request) []byte { url := strings.TrimLeft(r.URL.Path, "/") body := e.body if body != "" { if e.nova != nil { body = strings.Replace(body, "$ENDPOINT$", e.nova.endpointURL(true, "/"), -1) } body = strings.Replace(body, "$URL$", url, -1) body = strings.Replace(body, "$ERROR$", e.Error(), -1) if slash := strings.LastIndex(url, "/"); slash != -1 { body = strings.Replace(body, "$ID$", url[slash+1:], -1) } } return []byte(body) } func (e *errorResponse) ServeHTTP(w http.ResponseWriter, r *http.Request) { if e.contentType != "" { w.Header().Set("Content-Type", e.contentType) } body := e.requestBody(r) if e.headers != nil { for h, v := range e.headers { w.Header().Set(h, v) } } // workaround for https://code.google.com/p/go/issues/detail?id=4454 w.Header().Set("Content-Length", strconv.Itoa(len(body))) if e.code != 0 { w.WriteHeader(e.code) } if len(body) > 0 { w.Write(body) } } type novaHandler struct { n *Nova method func(n *Nova, w http.ResponseWriter, r *http.Request) error } func userInfo(i identityservice.IdentityService, r *http.Request) (*identityservice.UserInfo, error) { return i.FindUser(r.Header.Get(authToken)) } func (h *novaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { path := r.URL.Path // handle invalid X-Auth-Token header _, err := userInfo(h.n.IdentityService, r) if err != nil { errUnauthorized.ServeHTTP(w, r) return } // handle trailing slash in the path if strings.HasSuffix(path, "/") && path != "/" { errNotFound.ServeHTTP(w, r) return } err = h.method(h.n, w, r) if err == nil { return } var resp http.Handler if err == testservices.RateLimitExceededError { resp = errRateLimitExceeded } else if err == testservices.NoMoreFloatingIPs { resp = errNoMoreFloatingIPs } else if err == testservices.IPLimitExceeded { resp = errIPLimitExceeded } else { resp, _ = err.(http.Handler) if resp == nil { code, encodedErr := errorJSONEncode(err) resp = &errorResponse{ code, encodedErr, "application/json", err.Error(), nil, h.n, } } } resp.ServeHTTP(w, r) } func writeResponse(w http.ResponseWriter, code int, body []byte) { // workaround for https://code.google.com/p/go/issues/detail?id=4454 w.Header().Set("Content-Length", strconv.Itoa(len(body))) w.WriteHeader(code) w.Write(body) } // sendJSON sends the specified response serialized as JSON. func sendJSON(code int, resp interface{}, w http.ResponseWriter, r *http.Request) error { data, err := json.Marshal(resp) if err != nil { return err } writeResponse(w, code, data) return nil } func (n *Nova) handler(method func(n *Nova, w http.ResponseWriter, r *http.Request) error) http.Handler { return &novaHandler{n, method} } func (n *Nova) handleRoot(w http.ResponseWriter, r *http.Request) error { if r.URL.Path == "/" { return errNoVersion } return errMultipleChoices } func (n *Nova) HandleRoot(w http.ResponseWriter, r *http.Request) { n.handler((*Nova).handleRoot).ServeHTTP(w, r) } // handleFlavors handles the flavors HTTP API. func (n *Nova) handleFlavors(w http.ResponseWriter, r *http.Request) error { switch r.Method { case "GET": if flavorId := path.Base(r.URL.Path); flavorId != "flavors" { flavor, err := n.flavor(flavorId) if err != nil { return errNotFound } resp := struct { Flavor nova.FlavorDetail `json:"flavor"` }{*flavor} return sendJSON(http.StatusOK, resp, w, r) } entities := n.allFlavorsAsEntities() if len(entities) == 0 { entities = []nova.Entity{} } resp := struct { Flavors []nova.Entity `json:"flavors"` }{entities} return sendJSON(http.StatusOK, resp, w, r) case "POST": if flavorId := path.Base(r.URL.Path); flavorId != "flavors" { return errNotFound } body, err := ioutil.ReadAll(r.Body) if err != nil { return err } if len(body) == 0 { return errBadRequest2 } return errNotImplemented case "PUT": if flavorId := path.Base(r.URL.Path); flavorId != "flavors" { return errNotFoundJSON } return errNotFound case "DELETE": if flavorId := path.Base(r.URL.Path); flavorId != "flavors" { return errForbidden } return errNotFound } return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path) } // handleFlavorsDetail handles the flavors/detail HTTP API. func (n *Nova) handleFlavorsDetail(w http.ResponseWriter, r *http.Request) error { switch r.Method { case "GET": if flavorId := path.Base(r.URL.Path); flavorId != "detail" { return errNotFound } flavors := n.allFlavors() if len(flavors) == 0 { flavors = []nova.FlavorDetail{} } resp := struct { Flavors []nova.FlavorDetail `json:"flavors"` }{flavors} return sendJSON(http.StatusOK, resp, w, r) case "POST": return errNotFound case "PUT": if flavorId := path.Base(r.URL.Path); flavorId != "detail" { return errNotFound } return errNotFoundJSON case "DELETE": if flavorId := path.Base(r.URL.Path); flavorId != "detail" { return errNotFound } return errForbidden } return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path) } // handleServerActions handles the servers//action HTTP API. func (n *Nova) handleServerActions(server *nova.ServerDetail, w http.ResponseWriter, r *http.Request) error { if server == nil { return errNotFound } body, err := ioutil.ReadAll(r.Body) if err != nil || len(body) == 0 { return errNotFound } var action struct { AddSecurityGroup *struct { Name string } RemoveSecurityGroup *struct { Name string } AddFloatingIP *struct { Address string } RemoveFloatingIP *struct { Address string } } if err := json.Unmarshal(body, &action); err != nil { return err } switch { case action.AddSecurityGroup != nil: name := action.AddSecurityGroup.Name group, err := n.securityGroupByName(name) if err != nil || n.hasServerSecurityGroup(server.Id, group.Id) { return errNotFound } if err := n.addServerSecurityGroup(server.Id, group.Id); err != nil { return err } writeResponse(w, http.StatusAccepted, nil) return nil case action.RemoveSecurityGroup != nil: name := action.RemoveSecurityGroup.Name group, err := n.securityGroupByName(name) if err != nil || !n.hasServerSecurityGroup(server.Id, group.Id) { return errNotFound } if err := n.removeServerSecurityGroup(server.Id, group.Id); err != nil { return err } writeResponse(w, http.StatusAccepted, nil) return nil case action.AddFloatingIP != nil: addr := action.AddFloatingIP.Address if n.hasServerFloatingIP(server.Id, addr) { return errNotFound } fip, err := n.floatingIPByAddr(addr) if err != nil { return errNotFound } if err := n.addServerFloatingIP(server.Id, fip.Id); err != nil { return err } writeResponse(w, http.StatusAccepted, nil) return nil case action.RemoveFloatingIP != nil: addr := action.RemoveFloatingIP.Address if !n.hasServerFloatingIP(server.Id, addr) { return errNotFound } fip, err := n.floatingIPByAddr(addr) if err != nil { return errNotFound } if err := n.removeServerFloatingIP(server.Id, fip.Id); err != nil { return err } writeResponse(w, http.StatusAccepted, nil) return nil } return fmt.Errorf("unknown server action: %q", string(body)) } // handleServerMetadata handles the servers//action HTTP API. func (n *Nova) handleServerMetadata(server *nova.ServerDetail, w http.ResponseWriter, r *http.Request) error { if server == nil { return errNotFound } body, err := ioutil.ReadAll(r.Body) if err != nil || len(body) == 0 { return errNotFound } var req struct { Metadata map[string]string `json:"metadata"` } if err := json.Unmarshal(body, &req); err != nil { return err } if err := n.setServerMetadata(server.Id, req.Metadata); err != nil { return err } writeResponse(w, http.StatusOK, nil) return nil } // newUUID generates a random UUID conforming to RFC 4122. func newUUID() (string, error) { uuid := make([]byte, 16) if _, err := io.ReadFull(rand.Reader, uuid); err != nil { return "", err } uuid[8] = uuid[8]&^0xc0 | 0x80 // variant bits; see section 4.1.1. uuid[6] = uuid[6]&^0xf0 | 0x40 // version 4; see section 4.1.3. return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]), nil } // noGroupError constructs a bad request response for an invalid group. func noGroupError(groupName, tenantId string) error { return &errorResponse{ http.StatusBadRequest, `{"badRequest": {"message": "Security group ` + groupName + ` not found for project ` + tenantId + `.", "code": 400}}`, "application/json; charset=UTF-8", "bad request URL", nil, nil, } } // handleRunServer handles creating and running a server. func (n *Nova) handleRunServer(body []byte, w http.ResponseWriter, r *http.Request) error { var req struct { Server struct { FlavorRef string ImageRef string Name string Metadata map[string]string SecurityGroups []map[string]string `json:"security_groups"` Networks []map[string]string AvailabilityZone string `json:"availability_zone"` } } if err := json.Unmarshal(body, &req); err != nil { return errBadRequest3 } if req.Server.Name == "" { return errBadRequestSrvName } if req.Server.ImageRef == "" { return errBadRequestSrvImage } if req.Server.FlavorRef == "" { return errBadRequestSrvFlavor } if az := req.Server.AvailabilityZone; az != "" { if !n.availabilityZones[az].State.Available { return testservices.AvailabilityZoneIsNotAvailable } } n.nextServerId++ id := strconv.Itoa(n.nextServerId) uuid, err := newUUID() if err != nil { return err } var groups []string if len(req.Server.SecurityGroups) > 0 { for _, group := range req.Server.SecurityGroups { groupName := group["name"] if sg, err := n.securityGroupByName(groupName); err != nil { return noGroupError(groupName, n.TenantId) } else { groups = append(groups, sg.Id) } } } // TODO(gz) some kind of sane handling of networks for _, network := range req.Server.Networks { networkId := network["uuid"] _, ok := n.networks[networkId] if !ok { return errNotFoundJSON } } // TODO(dimitern) - 2013-02-11 bug=1121684 // make sure flavor/image exist (if needed) flavor := nova.FlavorDetail{Id: req.Server.FlavorRef} n.buildFlavorLinks(&flavor) flavorEnt := nova.Entity{Id: flavor.Id, Links: flavor.Links} image := nova.Entity{Id: req.Server.ImageRef} timestr := time.Now().Format(time.RFC3339) userInfo, _ := userInfo(n.IdentityService, r) server := nova.ServerDetail{ Id: id, UUID: uuid, Name: req.Server.Name, TenantId: n.TenantId, UserId: userInfo.Id, HostId: "1", Image: image, Flavor: flavorEnt, Status: nova.StatusActive, Created: timestr, Updated: timestr, Addresses: make(map[string][]nova.IPAddress), AvailabilityZone: req.Server.AvailabilityZone, Metadata: req.Server.Metadata, } servers, err := n.allServers(nil) if err != nil { return err } nextServer := len(servers) + 1 n.buildServerLinks(&server) // set some IP addresses addr := fmt.Sprintf("127.10.0.%d", nextServer) server.Addresses["public"] = []nova.IPAddress{{4, addr, "fixed"}, {6, "::dead:beef:f00d", "fixed"}} addr = fmt.Sprintf("127.0.0.%d", nextServer) server.Addresses["private"] = []nova.IPAddress{{4, addr, "fixed"}, {6, "::face::000f", "fixed"}} if err := n.addServer(server); err != nil { return err } var resp struct { Server struct { SecurityGroups []map[string]string `json:"security_groups"` Id string `json:"id"` Links []nova.Link `json:"links"` AdminPass string `json:"adminPass"` } `json:"server"` } if len(req.Server.SecurityGroups) > 0 { for _, gid := range groups { if err := n.addServerSecurityGroup(id, gid); err != nil { return err } } resp.Server.SecurityGroups = req.Server.SecurityGroups } else { resp.Server.SecurityGroups = []map[string]string{{"name": "default"}} } resp.Server.Id = id resp.Server.Links = server.Links resp.Server.AdminPass = "secret" return sendJSON(http.StatusAccepted, resp, w, r) } // handleServers handles the servers HTTP API. func (n *Nova) handleServers(w http.ResponseWriter, r *http.Request) error { if strings.Contains(r.URL.Path, "os-volume_attachments") { switch r.Method { case "GET": return n.handleListVolumes(w, r) case "POST": return n.handleAttachVolumes(w, r) case "DELETE": return n.handleDetachVolumes(w, r) } } switch r.Method { case "GET": if suffix := path.Base(r.URL.Path); suffix != "servers" { groups := false serverId := "" if suffix == "os-security-groups" { // handle GET /servers//os-security-groups serverId = path.Base(strings.Replace(r.URL.Path, "/os-security-groups", "", 1)) groups = true } else { serverId = suffix } server, err := n.server(serverId) if err != nil { return err } if groups { srvGroups := n.allServerSecurityGroups(serverId) if len(srvGroups) == 0 { srvGroups = []nova.SecurityGroup{} } resp := struct { Groups []nova.SecurityGroup `json:"security_groups"` }{srvGroups} return sendJSON(http.StatusOK, resp, w, r) } resp := struct { Server nova.ServerDetail `json:"server"` }{*server} return sendJSON(http.StatusOK, resp, w, r) } f := make(filter) if err := r.ParseForm(); err == nil && len(r.Form) > 0 { for filterKey, filterValues := range r.Form { for _, value := range filterValues { f[filterKey] = value } } } entities, err := n.allServersAsEntities(f) if err != nil { return err } if len(entities) == 0 { entities = []nova.Entity{} } resp := struct { Servers []nova.Entity `json:"servers"` }{entities} return sendJSON(http.StatusOK, resp, w, r) case "POST": if suffix := path.Base(r.URL.Path); suffix != "servers" { serverId := "" if suffix == "action" { // handle POST /servers//action serverId = path.Base(strings.Replace(r.URL.Path, "/action", "", 1)) server, _ := n.server(serverId) return n.handleServerActions(server, w, r) } else if suffix == "metadata" { // handle POST /servers//metadata serverId = path.Base(strings.Replace(r.URL.Path, "/metadata", "", 1)) server, _ := n.server(serverId) return n.handleServerMetadata(server, w, r) } else { serverId = suffix } return errNotFound } body, err := ioutil.ReadAll(r.Body) if err != nil { return err } if len(body) == 0 { return errBadRequest2 } return n.handleRunServer(body, w, r) case "PUT": serverId := path.Base(r.URL.Path) if serverId == "servers" { return errNotFound } var req struct { Server struct { Name string `json:"name"` } `json:"server"` } body, err := ioutil.ReadAll(r.Body) if err != nil || len(body) == 0 { return errBadRequest2 } if err := json.Unmarshal(body, &req); err != nil { return err } err = n.updateServerName(serverId, req.Server.Name) if err != nil { return err } server, err := n.server(serverId) if err != nil { return err } var resp struct { Server nova.ServerDetail `json:"server"` } resp.Server = *server return sendJSON(http.StatusOK, resp, w, r) case "DELETE": if serverId := path.Base(r.URL.Path); serverId != "servers" { if _, err := n.server(serverId); err != nil { return errNotFoundJSON } if err := n.removeServer(serverId); err != nil { return err } writeResponse(w, http.StatusNoContent, nil) return nil } return errNotFound } return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path) } // handleServersDetail handles the servers/detail HTTP API. func (n *Nova) handleServersDetail(w http.ResponseWriter, r *http.Request) error { switch r.Method { case "GET": if serverId := path.Base(r.URL.Path); serverId != "detail" { return errNotFound } f := make(filter) if err := r.ParseForm(); err == nil && len(r.Form) > 0 { for filterKey, filterValues := range r.Form { for _, value := range filterValues { f[filterKey] = value } } } servers, err := n.allServers(f) if err != nil { return err } if len(servers) == 0 { servers = []nova.ServerDetail{} } resp := struct { Servers []nova.ServerDetail `json:"servers"` }{servers} return sendJSON(http.StatusOK, resp, w, r) case "POST": return errNotFound case "PUT": if serverId := path.Base(r.URL.Path); serverId != "detail" { return errNotFound } return errBadRequest2 case "DELETE": if serverId := path.Base(r.URL.Path); serverId != "detail" { return errNotFound } return errNotFoundJSON } return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path) } // processGroupId returns the group id from the given request. // If there was no group id specified in the path, it returns errNoGroupId func (n *Nova) processGroupId(w http.ResponseWriter, r *http.Request) (*nova.SecurityGroup, error) { if groupId := path.Base(r.URL.Path); groupId != "os-security-groups" { group, err := n.securityGroup(groupId) if err != nil { return nil, errNotFoundJSONSG } return group, nil } return nil, errNoGroupId } // handleSecurityGroups handles the os-security-groups HTTP API. func (n *Nova) handleSecurityGroups(w http.ResponseWriter, r *http.Request) error { switch r.Method { case "GET": group, err := n.processGroupId(w, r) if err == errNoGroupId { groups := n.allSecurityGroups() if len(groups) == 0 { groups = []nova.SecurityGroup{} } resp := struct { Groups []nova.SecurityGroup `json:"security_groups"` }{groups} return sendJSON(http.StatusOK, resp, w, r) } if err != nil { return err } resp := struct { Group nova.SecurityGroup `json:"security_group"` }{*group} return sendJSON(http.StatusOK, resp, w, r) case "POST": if groupId := path.Base(r.URL.Path); groupId != "os-security-groups" { return errNotFound } body, err := ioutil.ReadAll(r.Body) if err != nil || len(body) == 0 { return errBadRequest2 } var req struct { Group struct { Name string Description string } `json:"security_group"` } if err := json.Unmarshal(body, &req); err != nil { return err } else { _, err := n.securityGroupByName(req.Group.Name) if err == nil { return errBadRequestDuplicateValue } n.nextGroupId++ nextId := strconv.Itoa(n.nextGroupId) err = n.addSecurityGroup(nova.SecurityGroup{ Id: nextId, Name: req.Group.Name, Description: req.Group.Description, TenantId: n.TenantId, }) if err != nil { return err } group, err := n.securityGroup(nextId) if err != nil { return err } var resp struct { Group nova.SecurityGroup `json:"security_group"` } resp.Group = *group return sendJSON(http.StatusOK, resp, w, r) } case "PUT": if groupId := path.Base(r.URL.Path); groupId == "os-security-groups" { return errNotFound } group, err := n.processGroupId(w, r) if err != nil { return err } var req struct { Group struct { Name string Description string } `json:"security_group"` } body, err := ioutil.ReadAll(r.Body) if err != nil || len(body) == 0 { return errBadRequest2 } if err := json.Unmarshal(body, &req); err != nil { return err } err = n.updateSecurityGroup(nova.SecurityGroup{ Id: group.Id, Name: req.Group.Name, Description: req.Group.Description, TenantId: group.TenantId, }) if err != nil { return err } group, err = n.securityGroup(group.Id) if err != nil { return err } var resp struct { Group nova.SecurityGroup `json:"security_group"` } resp.Group = *group return sendJSON(http.StatusOK, resp, w, r) case "DELETE": if group, err := n.processGroupId(w, r); group != nil { if err := n.removeSecurityGroup(group.Id); err != nil { return err } writeResponse(w, http.StatusAccepted, nil) return nil } else if err == errNoGroupId { return errNotFound } else { return err } } return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path) } // handleSecurityGroupRules handles the os-security-group-rules HTTP API. func (n *Nova) handleSecurityGroupRules(w http.ResponseWriter, r *http.Request) error { switch r.Method { case "GET": return errNotFoundJSON case "POST": if ruleId := path.Base(r.URL.Path); ruleId != "os-security-group-rules" { return errNotFound } body, err := ioutil.ReadAll(r.Body) if err != nil || len(body) == 0 { return errBadRequest2 } var req struct { Rule nova.RuleInfo `json:"security_group_rule"` } if err = json.Unmarshal(body, &req); err != nil { return err } inrule := req.Rule group, err := n.securityGroup(inrule.ParentGroupId) if err != nil { return err // TODO: should be a 4XX error with details } for _, r := range group.Rules { // TODO: this logic is actually wrong, not what nova does at all // why are we reimplementing half of nova/api/openstack in go again? if r.IPProtocol != nil && *r.IPProtocol == inrule.IPProtocol && r.FromPort != nil && *r.FromPort == inrule.FromPort && r.ToPort != nil && *r.ToPort == inrule.ToPort { // TODO: Use a proper helper and sane error type return &errorResponse{ http.StatusBadRequest, fmt.Sprintf(`{"badRequest": {"message": "This rule already exists in group %s", "code": 400}}`, group.Id), "application/json; charset=UTF-8", "rule already exists", nil, nil, } } } n.nextRuleId++ nextId := strconv.Itoa(n.nextRuleId) err = n.addSecurityGroupRule(nextId, req.Rule) if err != nil { return err } rule, err := n.securityGroupRule(nextId) if err != nil { return err } var resp struct { Rule nova.SecurityGroupRule `json:"security_group_rule"` } resp.Rule = *rule return sendJSON(http.StatusOK, resp, w, r) case "PUT": if ruleId := path.Base(r.URL.Path); ruleId != "os-security-group-rules" { return errNotFoundJSON } return errNotFound case "DELETE": if ruleId := path.Base(r.URL.Path); ruleId != "os-security-group-rules" { if _, err := n.securityGroupRule(ruleId); err != nil { return errNotFoundJSONSGR } if err := n.removeSecurityGroupRule(ruleId); err != nil { return err } writeResponse(w, http.StatusAccepted, nil) return nil } return errNotFound } return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path) } // handleFloatingIPs handles the os-floating-ips HTTP API. func (n *Nova) handleFloatingIPs(w http.ResponseWriter, r *http.Request) error { switch r.Method { case "GET": if ipId := path.Base(r.URL.Path); ipId != "os-floating-ips" { fip, err := n.floatingIP(ipId) if err != nil { return errNotFoundJSON } resp := struct { IP nova.FloatingIP `json:"floating_ip"` }{*fip} return sendJSON(http.StatusOK, resp, w, r) } fips := n.allFloatingIPs() if len(fips) == 0 { fips = []nova.FloatingIP{} } resp := struct { IPs []nova.FloatingIP `json:"floating_ips"` }{fips} return sendJSON(http.StatusOK, resp, w, r) case "POST": if ipId := path.Base(r.URL.Path); ipId != "os-floating-ips" { return errNotFound } n.nextIPId++ addr := fmt.Sprintf("10.0.0.%d", n.nextIPId) nextId := strconv.Itoa(n.nextIPId) fip := nova.FloatingIP{Id: nextId, IP: addr, Pool: "nova"} err := n.addFloatingIP(fip) if err != nil { return err } resp := struct { IP nova.FloatingIP `json:"floating_ip"` }{fip} return sendJSON(http.StatusOK, resp, w, r) case "PUT": if ipId := path.Base(r.URL.Path); ipId != "os-floating-ips" { return errNotFoundJSON } return errNotFound case "DELETE": if ipId := path.Base(r.URL.Path); ipId != "os-floating-ips" { if err := n.removeFloatingIP(ipId); err == nil { writeResponse(w, http.StatusAccepted, nil) return nil } return errNotFoundJSON } return errNotFound } return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path) } // handleNetworks handles the os-networks HTTP API. func (n *Nova) handleNetworks(w http.ResponseWriter, r *http.Request) error { switch r.Method { case "GET": if ipId := path.Base(r.URL.Path); ipId != "os-networks" { // TODO(gz): handle listing a single group return errNotFoundJSON } nets := n.allNetworks() if len(nets) == 0 { nets = []nova.Network{} } resp := struct { Network []nova.Network `json:"networks"` }{nets} return sendJSON(http.StatusOK, resp, w, r) // TODO(gz): proper handling of other methods } return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path) } // handleAvailabilityZones handles the os-availability-zone HTTP API. func (n *Nova) handleAvailabilityZones(w http.ResponseWriter, r *http.Request) error { switch r.Method { case "GET": if ipId := path.Base(r.URL.Path); ipId != "os-availability-zone" { return errNotFoundJSON } zones := n.allAvailabilityZones() if len(zones) == 0 { // If there are no availability zones defined, act as // if we don't support the availability zones extension. return errNotFoundJSON } resp := struct { Zones []nova.AvailabilityZone `json:"availabilityZoneInfo"` }{zones} return sendJSON(http.StatusOK, resp, w, r) } return fmt.Errorf("unknown request method %q for %s", r.Method, r.URL.Path) } func (n *Nova) handleAttachVolumes(w http.ResponseWriter, r *http.Request) error { serverId := path.Base(strings.Replace(r.URL.Path, "/os-volume_attachments", "", 1)) bodyBytes, err := ioutil.ReadAll(r.Body) if err != nil { return err } var attachment struct { VolumeAttachment nova.VolumeAttachment `json:"volumeAttachment"` } if err := json.Unmarshal(bodyBytes, &attachment); err != nil { return err } if attachment.VolumeAttachment.Device != nil { if !regexpVolumeAttachmentDevice.MatchString(*attachment.VolumeAttachment.Device) { message := fmt.Sprintf( "Invalid input for field/attribute device. Value: '%s' does not match '%s'", *attachment.VolumeAttachment.Device, regexpVolumeAttachmentDevice, ) return &errorResponse{ http.StatusBadRequest, fmt.Sprintf(`{"badRequest": {"message": "%s", "code": 400}}`, message), "application/json; charset=UTF-8", message, nil, nil, } } } var additionalProperties []string if attachment.VolumeAttachment.ServerId != "" { additionalProperties = append(additionalProperties, "'serverId'") } if len(additionalProperties) > 0 { message := fmt.Sprintf( "Additional properties are not allowed (%s were unexpected)", strings.Join(additionalProperties, ", "), ) return &errorResponse{ http.StatusBadRequest, fmt.Sprintf(`{"badRequest": {"message": "%s", "code": 400}}`, message), "application/json; charset=UTF-8", message, nil, nil, } } n.nextAttachmentId++ attachment.VolumeAttachment.Id = fmt.Sprintf("%d", n.nextAttachmentId) attachment.VolumeAttachment.ServerId = serverId serverVols := n.serverIdToAttachedVolumes[serverId] serverVols = append(serverVols, attachment.VolumeAttachment) n.serverIdToAttachedVolumes[serverId] = serverVols // Echo the request back with an attachment ID. resp, err := json.Marshal(&attachment) if err != nil { return err } _, err = w.Write(resp) return err } func (n *Nova) handleDetachVolumes(w http.ResponseWriter, r *http.Request) error { attachId := path.Base(r.URL.Path) serverId := path.Base(strings.Replace(r.URL.Path, "/os-volume_attachments/"+attachId, "", 1)) serverVols := n.serverIdToAttachedVolumes[serverId] for volIdx, vol := range serverVols { if vol.Id == attachId { serverVols = append(serverVols[:volIdx], serverVols[volIdx+1:]...) n.serverIdToAttachedVolumes[serverId] = serverVols writeResponse(w, http.StatusAccepted, nil) return nil } } return errors.NewNotFoundf(nil, nil, "no such attachment id: %v", attachId) } func (n *Nova) handleListVolumes(w http.ResponseWriter, r *http.Request) error { serverId := path.Base(strings.Replace(r.URL.Path, "/os-volume_attachments", "", 1)) serverVols := n.serverIdToAttachedVolumes[serverId] resp, err := json.Marshal(struct { VolumeAttachments []nova.VolumeAttachment `json:"volumeAttachments"` }{serverVols}) if err != nil { return err } _, err = w.Write(resp) return err } // SetupHTTP attaches all the needed handlers to provide the HTTP API. func (n *Nova) SetupHTTP(mux *http.ServeMux) { handlers := map[string]http.Handler{ "/$v/": errBadRequest, "/$v/$t/": errNotFound, "/$v/$t/flavors": n.handler((*Nova).handleFlavors), "/$v/$t/flavors/detail": n.handler((*Nova).handleFlavorsDetail), "/$v/$t/servers": n.handler((*Nova).handleServers), "/$v/$t/servers/detail": n.handler((*Nova).handleServersDetail), "/$v/$t/os-availability-zone": n.handler((*Nova).handleAvailabilityZones), } if !n.useNeutronNetworking { handlers["/$v/$t/os-security-groups"] = n.handler((*Nova).handleSecurityGroups) handlers["/$v/$t/os-security-group-rules"] = n.handler((*Nova).handleSecurityGroupRules) handlers["/$v/$t/os-floating-ips"] = n.handler((*Nova).handleFloatingIPs) handlers["/$v/$t/os-networks"] = n.handler((*Nova).handleNetworks) } for path, h := range handlers { path = strings.Replace(path, "$v", n.VersionPath, 1) path = strings.Replace(path, "$t", n.TenantId, 1) if !strings.HasSuffix(path, "/") { mux.Handle(path+"/", h) } mux.Handle(path, h) } } func (n *Nova) SetupRootHandler(mux *http.ServeMux) { mux.Handle("/", n.handler((*Nova).handleRoot)) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/novaservice/service_http_test.go000066400000000000000000001214501414605405700312530ustar00rootroot00000000000000// Nova double testing service - HTTP API tests package novaservice import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "sort" "strconv" "strings" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/nova" "gopkg.in/goose.v1/testing/httpsuite" "gopkg.in/goose.v1/testservices/hook" "gopkg.in/goose.v1/testservices/identityservice" "gopkg.in/goose.v1/testservices/neutronmodel" ) type NovaHTTPSuite struct { httpsuite.HTTPSuite service *Nova token string useNeutronNetworking bool } var _ = gc.Suite(&NovaHTTPSuite{useNeutronNetworking: false}) type NovaHTTPSSuite struct { httpsuite.HTTPSuite service *Nova token string useNeutronNetworking bool } var _ = gc.Suite(&NovaHTTPSSuite{HTTPSuite: httpsuite.HTTPSuite{UseTLS: true}, useNeutronNetworking: true}) func (s *NovaHTTPSuite) SetUpSuite(c *gc.C) { s.HTTPSuite.SetUpSuite(c) identityDouble := identityservice.NewUserPass() userInfo := identityDouble.AddUser("fred", "secret", "tenant", "default") s.token = userInfo.Token s.service = New(s.Server.URL, versionPath, userInfo.TenantId, region, identityDouble, nil) if s.useNeutronNetworking { c.Logf("Nova Service using Neutron Networking") s.service.AddNeutronModel(neutronmodel.New()) } else { c.Logf("Nova Service using Nova Networking") } } func (s *NovaHTTPSuite) TearDownSuite(c *gc.C) { s.HTTPSuite.TearDownSuite(c) } func (s *NovaHTTPSuite) SetUpTest(c *gc.C) { s.HTTPSuite.SetUpTest(c) s.service.SetupHTTP(s.Mux) // this is otherwise handled not directly by nova test service // but by openstack that tries for / before. s.Mux.Handle("/", s.service.handler((*Nova).handleRoot)) } func (s *NovaHTTPSuite) TearDownTest(c *gc.C) { s.HTTPSuite.TearDownTest(c) } // assertJSON asserts the passed http.Response's body can be // unmarshalled into the given expected object, populating it with the // successfully parsed data. func assertJSON(c *gc.C, resp *http.Response, expected interface{}) { body, err := ioutil.ReadAll(resp.Body) defer resp.Body.Close() c.Assert(err, gc.IsNil) err = json.Unmarshal(body, &expected) c.Assert(err, gc.IsNil) } // assertBody asserts the passed http.Response's body matches the // expected response, replacing any variables in the expected body. func assertBody(c *gc.C, resp *http.Response, expected *errorResponse) { body, err := ioutil.ReadAll(resp.Body) defer resp.Body.Close() c.Assert(err, gc.IsNil) expBody := expected.requestBody(resp.Request) // cast to string for easier asserts debugging c.Assert(string(body), gc.Equals, string(expBody)) } // sendRequest constructs an HTTP request from the parameters and // sends it, returning the response or an error. func (s *NovaHTTPSuite) sendRequest(method, url string, body []byte, headers http.Header) (*http.Response, error) { if !strings.HasPrefix(url, "http") { url = "http://" + s.service.Hostname + strings.TrimLeft(url, "/") } req, err := http.NewRequest(method, url, bytes.NewReader(body)) if err != nil { return nil, err } for header, values := range headers { for _, value := range values { req.Header.Add(header, value) } } // workaround for https://code.google.com/p/go/issues/detail?id=4454 req.Header.Set("Content-Length", strconv.Itoa(len(body))) return http.DefaultClient.Do(req) } // authRequest is a shortcut for sending requests with pre-set token // header and correct version prefix and tenant ID in the URL. func (s *NovaHTTPSuite) authRequest(method, path string, body []byte, headers http.Header) (*http.Response, error) { if headers == nil { headers = make(http.Header) } headers.Set(authToken, s.token) url := s.service.endpointURL(true, path) return s.sendRequest(method, url, body, headers) } // jsonRequest serializes the passed body object to JSON and sends a // the request with authRequest(). func (s *NovaHTTPSuite) jsonRequest(method, path string, body interface{}, headers http.Header) (*http.Response, error) { jsonBody, err := json.Marshal(body) if err != nil { return nil, err } return s.authRequest(method, path, jsonBody, headers) } // setHeader creates http.Header map, sets the given header, and // returns the map. func setHeader(header, value string) http.Header { h := make(http.Header) h.Set(header, value) return h } // SimpleTest defines a simple request without a body and expected response. type SimpleTest struct { unauth bool method string url string headers http.Header expect *errorResponse } func (s *NovaHTTPSuite) simpleTests() []SimpleTest { var simpleTests = []SimpleTest{ { unauth: true, method: "GET", url: "/any", headers: make(http.Header), expect: errUnauthorized, }, { unauth: true, method: "POST", url: "/any", headers: setHeader(authToken, "phony"), expect: errUnauthorized, }, { unauth: true, method: "GET", url: "/any", headers: setHeader(authToken, s.token), expect: errMultipleChoices, }, { unauth: true, method: "POST", url: "/any/unknown/one", headers: setHeader(authToken, s.token), expect: errMultipleChoices, }, { method: "POST", url: "/any/unknown/one", expect: errNotFound, }, { unauth: true, method: "GET", url: versionPath + "/phony_token", headers: setHeader(authToken, s.token), expect: errBadRequest, }, { method: "GET", url: "/flavors/", expect: errNotFound, }, { method: "GET", url: "/flavors/invalid", expect: errNotFound, }, { method: "POST", url: "/flavors", expect: errBadRequest2, }, { method: "POST", url: "/flavors/invalid", expect: errNotFound, }, { method: "PUT", url: "/flavors", expect: errNotFound, }, { method: "PUT", url: "/flavors/invalid", expect: errNotFoundJSON, }, { method: "DELETE", url: "/flavors", expect: errNotFound, }, { method: "DELETE", url: "/flavors/invalid", expect: errForbidden, }, { method: "GET", url: "/flavors/detail/invalid", expect: errNotFound, }, { method: "POST", url: "/flavors/detail", expect: errNotFound, }, { method: "POST", url: "/flavors/detail/invalid", expect: errNotFound, }, { method: "PUT", url: "/flavors/detail", expect: errNotFoundJSON, }, { method: "PUT", url: "/flavors/detail/invalid", expect: errNotFound, }, { method: "DELETE", url: "/flavors/detail", expect: errForbidden, }, { method: "DELETE", url: "/flavors/detail/invalid", expect: errNotFound, }, { method: "GET", url: "/servers/invalid", expect: &errorResponse{code: 404, body: "{\"itemNotFound\":{\"message\":\"No such server \\\"invalid\\\"\", \"code\":404}}"}, }, { method: "POST", url: "/servers", expect: errBadRequest2, }, { method: "POST", url: "/servers/invalid", expect: errNotFound, }, { method: "PUT", url: "/servers", expect: errNotFound, }, { method: "PUT", url: "/servers/invalid", expect: errBadRequest2, }, { method: "DELETE", url: "/servers", expect: errNotFound, }, { method: "DELETE", url: "/servers/invalid", expect: errNotFoundJSON, }, { method: "GET", url: "/servers/detail/invalid", expect: errNotFound, }, { method: "POST", url: "/servers/detail", expect: errNotFound, }, { method: "POST", url: "/servers/detail/invalid", expect: errNotFound, }, { method: "PUT", url: "/servers/detail", expect: errBadRequest2, }, { method: "PUT", url: "/servers/detail/invalid", expect: errNotFound, }, { method: "DELETE", url: "/servers/detail", expect: errNotFoundJSON, }, { method: "DELETE", url: "/servers/detail/invalid", expect: errNotFound, }, } return simpleTests } func (s *NovaHTTPSuite) simpleNovaNetworkingTests() []SimpleTest { var simpleTests = []SimpleTest{ { method: "GET", url: "/os-security-groups/42", expect: errNotFoundJSONSG, }, { method: "POST", url: "/os-security-groups", expect: errBadRequest2, }, { method: "POST", url: "/os-security-groups/invalid", expect: errNotFound, }, { method: "PUT", url: "/os-security-groups", expect: errNotFound, }, { method: "PUT", url: "/os-security-groups/invalid", expect: errNotFoundJSONSG, }, { method: "DELETE", url: "/os-security-groups", expect: errNotFound, }, { method: "DELETE", url: "/os-security-groups/42", expect: errNotFoundJSONSG, }, { method: "GET", url: "/os-security-group-rules", expect: errNotFoundJSON, }, { method: "GET", url: "/os-security-group-rules/invalid", expect: errNotFoundJSON, }, { method: "GET", url: "/os-security-group-rules/42", expect: errNotFoundJSON, }, { method: "POST", url: "/os-security-group-rules", expect: errBadRequest2, }, { method: "POST", url: "/os-security-group-rules/invalid", expect: errNotFound, }, { method: "PUT", url: "/os-security-group-rules", expect: errNotFound, }, { method: "PUT", url: "/os-security-group-rules/invalid", expect: errNotFoundJSON, }, { method: "DELETE", url: "/os-security-group-rules", expect: errNotFound, }, { method: "DELETE", url: "/os-security-group-rules/42", expect: errNotFoundJSONSGR, }, { method: "GET", url: "/os-floating-ips/42", expect: errNotFoundJSON, }, { method: "POST", url: "/os-floating-ips/invalid", expect: errNotFound, }, { method: "PUT", url: "/os-floating-ips", expect: errNotFound, }, { method: "PUT", url: "/os-floating-ips/invalid", expect: errNotFoundJSON, }, { method: "DELETE", url: "/os-floating-ips", expect: errNotFound, }, { method: "DELETE", url: "/os-floating-ips/invalid", expect: errNotFoundJSON, }, } return simpleTests } func (s *NovaHTTPSuite) TestSimpleRequestTests(c *gc.C) { s.runSimpleTests(c, s.simpleTests()) if !s.useNeutronNetworking { s.runSimpleTests(c, s.simpleNovaNetworkingTests()) } } func (s *NovaHTTPSuite) runSimpleTests(c *gc.C, simpleTests []SimpleTest) { for i, t := range simpleTests { c.Logf("#%d. %s %s -> %d", i, t.method, t.url, t.expect.code) if t.headers == nil { t.headers = make(http.Header) t.headers.Set(authToken, s.token) } var ( resp *http.Response err error ) if t.unauth { resp, err = s.sendRequest(t.method, t.url, nil, t.headers) } else { resp, err = s.authRequest(t.method, t.url, nil, t.headers) } c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, t.expect.code) assertBody(c, resp, t.expect) } fmt.Printf("total: %d\n", len(simpleTests)) } func (s *NovaHTTPSuite) TestGetFlavors(c *gc.C) { // The test service has 3 default flavours. var expected struct { Flavors []nova.Entity } resp, err := s.authRequest("GET", "/flavors", nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) c.Assert(expected.Flavors, gc.HasLen, 3) entities := s.service.allFlavorsAsEntities() c.Assert(entities, gc.HasLen, 3) sort.Sort(nova.EntitySortBy{Attr: "Id", Entities: expected.Flavors}) sort.Sort(nova.EntitySortBy{Attr: "Id", Entities: entities}) c.Assert(expected.Flavors, gc.DeepEquals, entities) var expectedFlavor struct { Flavor nova.FlavorDetail } resp, err = s.authRequest("GET", "/flavors/1", nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expectedFlavor) c.Assert(expectedFlavor.Flavor.Name, gc.Equals, "m1.tiny") } func (s *NovaHTTPSuite) TestGetFlavorsDetail(c *gc.C) { // The test service has 3 default flavours. flavors := s.service.allFlavors() c.Assert(flavors, gc.HasLen, 3) var expected struct { Flavors []nova.FlavorDetail } resp, err := s.authRequest("GET", "/flavors/detail", nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) c.Assert(expected.Flavors, gc.HasLen, 3) sort.Sort(nova.FlavorDetailSortBy{Attr: "Id", FlavorDetails: expected.Flavors}) sort.Sort(nova.FlavorDetailSortBy{Attr: "Id", FlavorDetails: flavors}) c.Assert(expected.Flavors, gc.DeepEquals, flavors) resp, err = s.authRequest("GET", "/flavors/detail/1", nil, nil) c.Assert(err, gc.IsNil) assertBody(c, resp, errNotFound) } func (s *NovaHTTPSuite) TestGetServers(c *gc.C) { entities, err := s.service.allServersAsEntities(nil) c.Assert(err, gc.IsNil) c.Assert(entities, gc.HasLen, 0) var expected struct { Servers []nova.Entity } resp, err := s.authRequest("GET", "/servers", nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) c.Assert(expected.Servers, gc.HasLen, 0) servers := []nova.ServerDetail{ {Id: "sr1", Name: "server 1"}, {Id: "sr2", Name: "server 2"}, } for i, server := range servers { s.service.buildServerLinks(&server) servers[i] = server err := s.service.addServer(server) c.Assert(err, gc.IsNil) defer s.service.removeServer(server.Id) } entities, err = s.service.allServersAsEntities(nil) c.Assert(err, gc.IsNil) resp, err = s.authRequest("GET", "/servers", nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) c.Assert(expected.Servers, gc.HasLen, 2) if expected.Servers[0].Id != entities[0].Id { expected.Servers[0], expected.Servers[1] = expected.Servers[1], expected.Servers[0] } c.Assert(expected.Servers, gc.DeepEquals, entities) var expectedServer struct { Server nova.ServerDetail } resp, err = s.authRequest("GET", "/servers/sr1", nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expectedServer) c.Assert(expectedServer.Server, gc.DeepEquals, servers[0]) } func (s *NovaHTTPSuite) TestGetServersWithFilters(c *gc.C) { entities, err := s.service.allServersAsEntities(nil) c.Assert(err, gc.IsNil) c.Assert(entities, gc.HasLen, 0) var expected struct { Servers []nova.Entity } url := "/servers?status=RESCUE&status=BUILD&name=srv2&name=srv1" resp, err := s.authRequest("GET", url, nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) c.Assert(expected.Servers, gc.HasLen, 0) servers := []nova.ServerDetail{ {Id: "sr1", Name: "srv1", Status: nova.StatusBuild}, {Id: "sr2", Name: "srv2", Status: nova.StatusRescue}, {Id: "sr3", Name: "srv3", Status: nova.StatusActive}, } for i, server := range servers { s.service.buildServerLinks(&server) servers[i] = server err := s.service.addServer(server) c.Assert(err, gc.IsNil) defer s.service.removeServer(server.Id) } resp, err = s.authRequest("GET", url, nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) c.Assert(expected.Servers, gc.HasLen, 1) c.Assert(expected.Servers[0].Id, gc.Equals, servers[0].Id) c.Assert(expected.Servers[0].Name, gc.Equals, servers[0].Name) } func (s *NovaHTTPSuite) TestGetServersWithBadFilter(c *gc.C) { url := "/servers?name=(server" resp, err := s.authRequest("GET", url, nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusInternalServerError) type novaError struct { Code int Message string } var expected struct { novaError `json:"computeFault"` } assertJSON(c, resp, &expected) c.Check(expected.Code, gc.Equals, 500) c.Check(expected.Message, gc.Matches, `error parsing.*\(server.*`) } func (s *NovaHTTPSuite) TestGetServersPatchMatch(c *gc.C) { cleanup := s.service.RegisterControlPoint( "matchServers", func(sc hook.ServiceControl, args ...interface{}) error { return fmt.Errorf("Unexpected error") }, ) defer cleanup() resp, err := s.authRequest("GET", "/servers", nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusInternalServerError) type novaError struct { Code int Message string } var expected struct { novaError `json:"computeFault"` } assertJSON(c, resp, &expected) c.Check(expected.Code, gc.Equals, 500) c.Check(expected.Message, gc.Equals, "Unexpected error") } func (s *NovaHTTPSuite) TestNewUUID(c *gc.C) { uuid, err := newUUID() c.Assert(err, gc.IsNil) var p1, p2, p3, p4, p5 string num, err := fmt.Sscanf(uuid, "%8x-%4x-%4x-%4x-%12x", &p1, &p2, &p3, &p4, &p5) c.Assert(err, gc.IsNil) c.Assert(num, gc.Equals, 5) uuid2, err := newUUID() c.Assert(err, gc.IsNil) c.Assert(uuid2, gc.Not(gc.Equals), uuid) } func (s *NovaHTTPSuite) assertAddresses(c *gc.C, serverId string) { server, err := s.service.server(serverId) c.Assert(err, gc.IsNil) c.Assert(server.Addresses, gc.HasLen, 2) c.Assert(server.Addresses["public"], gc.HasLen, 2) c.Assert(server.Addresses["private"], gc.HasLen, 2) for network, addresses := range server.Addresses { for _, addr := range addresses { if addr.Version == 4 && network == "public" { c.Assert(addr.Address, gc.Matches, `127\.10\.0\.\d{1,3}`) } else if addr.Version == 4 && network == "private" { c.Assert(addr.Address, gc.Matches, `127\.0\.0\.\d{1,3}`) } } } } func (s *NovaHTTPSuite) TestRunServer(c *gc.C) { entities, err := s.service.allServersAsEntities(nil) c.Assert(err, gc.IsNil) c.Assert(entities, gc.HasLen, 0) var req struct { Server struct { FlavorRef string `json:"flavorRef"` ImageRef string `json:"imageRef"` Name string `json:"name"` SecurityGroups []map[string]string `json:"security_groups"` } `json:"server"` } resp, err := s.jsonRequest("POST", "/servers", req, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusBadRequest) assertBody(c, resp, errBadRequestSrvName) req.Server.Name = "srv1" resp, err = s.jsonRequest("POST", "/servers", req, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusBadRequest) assertBody(c, resp, errBadRequestSrvImage) req.Server.ImageRef = "image" resp, err = s.jsonRequest("POST", "/servers", req, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusBadRequest) assertBody(c, resp, errBadRequestSrvFlavor) req.Server.FlavorRef = "flavor" var expected struct { Server struct { SecurityGroups []map[string]string `json:"security_groups"` Id string Links []nova.Link AdminPass string } } resp, err = s.jsonRequest("POST", "/servers", req, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusAccepted) assertJSON(c, resp, &expected) c.Assert(expected.Server.SecurityGroups, gc.HasLen, 1) c.Assert(expected.Server.SecurityGroups[0]["name"], gc.Equals, "default") c.Assert(expected.Server.Id, gc.Not(gc.Equals), "") c.Assert(expected.Server.Links, gc.HasLen, 2) c.Assert(expected.Server.AdminPass, gc.Not(gc.Equals), "") s.assertAddresses(c, expected.Server.Id) srv, err := s.service.server(expected.Server.Id) c.Assert(err, gc.IsNil) c.Assert(srv.Links, gc.DeepEquals, expected.Server.Links) s.service.removeServer(srv.Id) req.Server.Name = "test2" req.Server.SecurityGroups = []map[string]string{ {"name": "default"}, {"name": "group1"}, {"name": "group2"}, } err = s.service.addSecurityGroup(nova.SecurityGroup{Id: "1", Name: "group1"}) c.Assert(err, gc.IsNil) defer s.service.removeSecurityGroup("1") err = s.service.addSecurityGroup(nova.SecurityGroup{Id: "2", Name: "group2"}) c.Assert(err, gc.IsNil) defer s.service.removeSecurityGroup("2") resp, err = s.jsonRequest("POST", "/servers", req, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusAccepted) assertJSON(c, resp, &expected) c.Assert(expected.Server.SecurityGroups, gc.DeepEquals, req.Server.SecurityGroups) srv, err = s.service.server(expected.Server.Id) c.Assert(err, gc.IsNil) ok := s.service.hasServerSecurityGroup(srv.Id, "1") c.Assert(ok, gc.Equals, true) ok = s.service.hasServerSecurityGroup(srv.Id, "2") c.Assert(ok, gc.Equals, true) ok = s.service.hasServerSecurityGroup(srv.Id, "999") c.Assert(ok, gc.Equals, true) s.service.removeServerSecurityGroup(srv.Id, "1") s.service.removeServerSecurityGroup(srv.Id, "2") s.service.removeServerSecurityGroup(srv.Id, "999") s.service.removeServer(srv.Id) } func (s *NovaHTTPSuite) TestDeleteServer(c *gc.C) { server := nova.ServerDetail{Id: "sr1"} _, err := s.service.server(server.Id) c.Assert(err, gc.NotNil) err = s.service.addServer(server) c.Assert(err, gc.IsNil) defer s.service.removeServer(server.Id) resp, err := s.authRequest("DELETE", "/servers/sr1", nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusNoContent) _, err = s.service.server(server.Id) c.Assert(err, gc.NotNil) } func (s *NovaHTTPSuite) TestGetServersDetail(c *gc.C) { servers, err := s.service.allServers(nil) c.Assert(err, gc.IsNil) c.Assert(servers, gc.HasLen, 0) var expected struct { Servers []nova.ServerDetail `json:"servers"` } resp, err := s.authRequest("GET", "/servers/detail", nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) c.Assert(expected.Servers, gc.HasLen, 0) servers = []nova.ServerDetail{ {Id: "sr1", Name: "server 1"}, {Id: "sr2", Name: "server 2"}, } for i, server := range servers { s.service.buildServerLinks(&server) servers[i] = server err := s.service.addServer(server) c.Assert(err, gc.IsNil) defer s.service.removeServer(server.Id) } resp, err = s.authRequest("GET", "/servers/detail", nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) c.Assert(expected.Servers, gc.HasLen, 2) if expected.Servers[0].Id != servers[0].Id { expected.Servers[0], expected.Servers[1] = expected.Servers[1], expected.Servers[0] } c.Assert(expected.Servers, gc.DeepEquals, servers) resp, err = s.authRequest("GET", "/servers/detail/sr1", nil, nil) c.Assert(err, gc.IsNil) assertBody(c, resp, errNotFound) } func (s *NovaHTTPSuite) TestGetServersDetailWithFilters(c *gc.C) { servers, err := s.service.allServers(nil) c.Assert(err, gc.IsNil) c.Assert(servers, gc.HasLen, 0) var expected struct { Servers []nova.ServerDetail `json:"servers"` } url := "/servers/detail?status=RESCUE&status=BUILD&name=srv2&name=srv1" resp, err := s.authRequest("GET", url, nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) c.Assert(expected.Servers, gc.HasLen, 0) servers = []nova.ServerDetail{ {Id: "sr1", Name: "srv1", Status: nova.StatusBuild}, {Id: "sr2", Name: "srv2", Status: nova.StatusRescue}, {Id: "sr3", Name: "srv3", Status: nova.StatusActive}, } for i, server := range servers { s.service.buildServerLinks(&server) servers[i] = server err := s.service.addServer(server) c.Assert(err, gc.IsNil) defer s.service.removeServer(server.Id) } resp, err = s.authRequest("GET", url, nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) c.Assert(expected.Servers, gc.HasLen, 1) c.Assert(expected.Servers[0], gc.DeepEquals, servers[0]) } func (s *NovaHTTPSuite) TestGetSecurityGroups(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } // There is always a default security group. groups := s.service.allSecurityGroups() c.Assert(groups, gc.HasLen, 1) var expected struct { Groups []nova.SecurityGroup `json:"security_groups"` } resp, err := s.authRequest("GET", "/os-security-groups", nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) c.Assert(expected.Groups, gc.HasLen, 1) groups = []nova.SecurityGroup{ { Id: "1", Name: "group 1", TenantId: s.service.TenantId, Rules: []nova.SecurityGroupRule{}, }, { Id: "2", Name: "group 2", TenantId: s.service.TenantId, Rules: []nova.SecurityGroupRule{}, }, } for _, group := range groups { err := s.service.addSecurityGroup(group) c.Assert(err, gc.IsNil) defer s.service.removeSecurityGroup(group.Id) } resp, err = s.authRequest("GET", "/os-security-groups", nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) c.Assert(expected.Groups, gc.HasLen, len(groups)+1) checkGroupsInList(c, groups, expected.Groups) var expectedGroup struct { Group nova.SecurityGroup `json:"security_group"` } resp, err = s.authRequest("GET", "/os-security-groups/1", nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expectedGroup) c.Assert(expectedGroup.Group, gc.DeepEquals, groups[0]) } func (s *NovaHTTPSuite) TestAddSecurityGroup(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } group := nova.SecurityGroup{ Id: "1", Name: "group 1", Description: "desc", TenantId: s.service.TenantId, Rules: []nova.SecurityGroupRule{}, } _, err := s.service.securityGroup(group.Id) c.Assert(err, gc.NotNil) var req struct { Group struct { Name string `json:"name"` Description string `json:"description"` } `json:"security_group"` } req.Group.Name = group.Name req.Group.Description = group.Description var expected struct { Group nova.SecurityGroup `json:"security_group"` } resp, err := s.jsonRequest("POST", "/os-security-groups", req, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) c.Assert(expected.Group, gc.DeepEquals, group) err = s.service.removeSecurityGroup(group.Id) c.Assert(err, gc.IsNil) } func (s *NovaHTTPSuite) TestDeleteSecurityGroup(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } group := nova.SecurityGroup{Id: "1", Name: "group 1"} _, err := s.service.securityGroup(group.Id) c.Assert(err, gc.NotNil) err = s.service.addSecurityGroup(group) c.Assert(err, gc.IsNil) defer s.service.removeSecurityGroup(group.Id) resp, err := s.authRequest("DELETE", "/os-security-groups/1", nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusAccepted) _, err = s.service.securityGroup(group.Id) c.Assert(err, gc.NotNil) } func (s *NovaHTTPSuite) TestAddSecurityGroupRule(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } group1 := nova.SecurityGroup{Id: "1", Name: "src"} group2 := nova.SecurityGroup{Id: "2", Name: "tgt"} err := s.service.addSecurityGroup(group1) c.Assert(err, gc.IsNil) defer s.service.removeSecurityGroup(group1.Id) err = s.service.addSecurityGroup(group2) c.Assert(err, gc.IsNil) defer s.service.removeSecurityGroup(group2.Id) riIngress := nova.RuleInfo{ ParentGroupId: "1", FromPort: 1234, ToPort: 4321, IPProtocol: "tcp", Cidr: "1.2.3.4/5", } riGroup := nova.RuleInfo{ ParentGroupId: group2.Id, GroupId: &group1.Id, } iprange := make(map[string]string) iprange["cidr"] = riIngress.Cidr rule1 := nova.SecurityGroupRule{ Id: "1", ParentGroupId: group1.Id, FromPort: &riIngress.FromPort, ToPort: &riIngress.ToPort, IPProtocol: &riIngress.IPProtocol, IPRange: iprange, } rule2 := nova.SecurityGroupRule{ Id: "2", ParentGroupId: group2.Id, Group: nova.SecurityGroupRef{ Name: group1.Name, TenantId: s.service.TenantId, }, } ok := s.service.hasSecurityGroupRule(group1.Id, rule1.Id) c.Assert(ok, gc.Equals, false) ok = s.service.hasSecurityGroupRule(group2.Id, rule2.Id) c.Assert(ok, gc.Equals, false) var req struct { Rule nova.RuleInfo `json:"security_group_rule"` } req.Rule = riIngress var expected struct { Rule nova.SecurityGroupRule `json:"security_group_rule"` } resp, err := s.jsonRequest("POST", "/os-security-group-rules", req, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) c.Assert(expected.Rule.Id, gc.Equals, rule1.Id) c.Assert(expected.Rule.ParentGroupId, gc.Equals, rule1.ParentGroupId) c.Assert(expected.Rule.Group, gc.Equals, nova.SecurityGroupRef{}) c.Assert(*expected.Rule.FromPort, gc.Equals, *rule1.FromPort) c.Assert(*expected.Rule.ToPort, gc.Equals, *rule1.ToPort) c.Assert(*expected.Rule.IPProtocol, gc.Equals, *rule1.IPProtocol) c.Assert(expected.Rule.IPRange, gc.DeepEquals, rule1.IPRange) defer s.service.removeSecurityGroupRule(rule1.Id) req.Rule = riGroup resp, err = s.jsonRequest("POST", "/os-security-group-rules", req, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) c.Assert(expected.Rule.Id, gc.Equals, rule2.Id) c.Assert(expected.Rule.ParentGroupId, gc.Equals, rule2.ParentGroupId) c.Assert(expected.Rule.Group, gc.DeepEquals, rule2.Group) err = s.service.removeSecurityGroupRule(rule2.Id) c.Assert(err, gc.IsNil) } func (s *NovaHTTPSuite) TestDeleteSecurityGroupRule(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } group1 := nova.SecurityGroup{Id: "1", Name: "src"} group2 := nova.SecurityGroup{Id: "2", Name: "tgt"} err := s.service.addSecurityGroup(group1) c.Assert(err, gc.IsNil) defer s.service.removeSecurityGroup(group1.Id) err = s.service.addSecurityGroup(group2) c.Assert(err, gc.IsNil) defer s.service.removeSecurityGroup(group2.Id) riGroup := nova.RuleInfo{ ParentGroupId: group2.Id, GroupId: &group1.Id, } rule := nova.SecurityGroupRule{ Id: "1", ParentGroupId: group2.Id, Group: nova.SecurityGroupRef{ Name: group1.Name, TenantId: group1.TenantId, }, } err = s.service.addSecurityGroupRule(rule.Id, riGroup) c.Assert(err, gc.IsNil) resp, err := s.authRequest("DELETE", "/os-security-group-rules/1", nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusAccepted) ok := s.service.hasSecurityGroupRule(group2.Id, rule.Id) c.Assert(ok, gc.Equals, false) } func (s *NovaHTTPSuite) TestAddServerSecurityGroup(c *gc.C) { group := nova.SecurityGroup{Id: "1", Name: "group"} err := s.service.addSecurityGroup(group) c.Assert(err, gc.IsNil) defer s.service.removeSecurityGroup(group.Id) server := nova.ServerDetail{Id: "sr1"} err = s.service.addServer(server) c.Assert(err, gc.IsNil) defer s.service.removeServer(server.Id) ok := s.service.hasServerSecurityGroup(server.Id, group.Id) c.Assert(ok, gc.Equals, false) var req struct { Group struct { Name string `json:"name"` } `json:"addSecurityGroup"` } req.Group.Name = group.Name resp, err := s.jsonRequest("POST", "/servers/"+server.Id+"/action", req, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusAccepted) ok = s.service.hasServerSecurityGroup(server.Id, group.Id) c.Assert(ok, gc.Equals, true) err = s.service.removeServerSecurityGroup(server.Id, group.Id) c.Assert(err, gc.IsNil) } func (s *NovaHTTPSuite) TestGetServerSecurityGroups(c *gc.C) { server := nova.ServerDetail{Id: "sr1"} groups := []nova.SecurityGroup{ { Id: "1", Name: "group1", TenantId: s.service.TenantId, Rules: []nova.SecurityGroupRule{}, }, { Id: "2", Name: "group2", TenantId: s.service.TenantId, Rules: []nova.SecurityGroupRule{}, }, } srvGroups := s.service.allServerSecurityGroups(server.Id) c.Assert(srvGroups, gc.HasLen, 0) err := s.service.addServer(server) c.Assert(err, gc.IsNil) defer s.service.removeServer(server.Id) for _, group := range groups { err = s.service.addSecurityGroup(group) c.Assert(err, gc.IsNil) defer s.service.removeSecurityGroup(group.Id) err = s.service.addServerSecurityGroup(server.Id, group.Id) c.Assert(err, gc.IsNil) defer s.service.removeServerSecurityGroup(server.Id, group.Id) } srvGroups = s.service.allServerSecurityGroups(server.Id) var expected struct { Groups []nova.SecurityGroup `json:"security_groups"` } resp, err := s.authRequest("GET", "/servers/"+server.Id+"/os-security-groups", nil, nil) c.Assert(err, gc.IsNil) assertJSON(c, resp, &expected) // nova networking doesn't know about neutron egress direction rules, // created by default with a new security group if s.service.useNeutronNetworking { expected.Groups[0].Rules = []nova.SecurityGroupRule{} expected.Groups[1].Rules = []nova.SecurityGroupRule{} } c.Assert(expected.Groups, gc.DeepEquals, groups) } func (s *NovaHTTPSuite) TestDeleteServerSecurityGroup(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } group := nova.SecurityGroup{Id: "1", Name: "group"} err := s.service.addSecurityGroup(group) c.Assert(err, gc.IsNil) defer s.service.removeSecurityGroup(group.Id) server := nova.ServerDetail{Id: "sr1"} err = s.service.addServer(server) c.Assert(err, gc.IsNil) defer s.service.removeServer(server.Id) ok := s.service.hasServerSecurityGroup(server.Id, group.Id) c.Assert(ok, gc.Equals, false) err = s.service.addServerSecurityGroup(server.Id, group.Id) c.Assert(err, gc.IsNil) var req struct { Group struct { Name string `json:"name"` } `json:"removeSecurityGroup"` } req.Group.Name = group.Name resp, err := s.jsonRequest("POST", "/servers/"+server.Id+"/action", req, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusAccepted) ok = s.service.hasServerSecurityGroup(server.Id, group.Id) c.Assert(ok, gc.Equals, false) } func (s *NovaHTTPSuite) TestPostFloatingIP(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } fip := nova.FloatingIP{Id: "1", IP: "10.0.0.1", Pool: "nova"} c.Assert(s.service.allFloatingIPs(), gc.HasLen, 0) var expected struct { IP nova.FloatingIP `json:"floating_ip"` } resp, err := s.authRequest("POST", "/os-floating-ips", nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) c.Assert(expected.IP, gc.DeepEquals, fip) err = s.service.removeFloatingIP(fip.Id) c.Assert(err, gc.IsNil) } func (s *NovaHTTPSuite) TestGetFloatingIPs(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } c.Assert(s.service.allFloatingIPs(), gc.HasLen, 0) var expected struct { IPs []nova.FloatingIP `json:"floating_ips"` } resp, err := s.authRequest("GET", "/os-floating-ips", nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) c.Assert(expected.IPs, gc.HasLen, 0) fips := []nova.FloatingIP{ {Id: "1", IP: "1.2.3.4", Pool: "nova"}, {Id: "2", IP: "4.3.2.1", Pool: "nova"}, } for _, fip := range fips { err := s.service.addFloatingIP(fip) defer s.service.removeFloatingIP(fip.Id) c.Assert(err, gc.IsNil) } resp, err = s.authRequest("GET", "/os-floating-ips", nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expected) if expected.IPs[0].Id != fips[0].Id { expected.IPs[0], expected.IPs[1] = expected.IPs[1], expected.IPs[0] } c.Assert(expected.IPs, gc.DeepEquals, fips) var expectedIP struct { IP nova.FloatingIP `json:"floating_ip"` } resp, err = s.authRequest("GET", "/os-floating-ips/1", nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) assertJSON(c, resp, &expectedIP) c.Assert(expectedIP.IP, gc.DeepEquals, fips[0]) } func (s *NovaHTTPSuite) TestDeleteFloatingIP(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } fip := nova.FloatingIP{Id: "1", IP: "10.0.0.1", Pool: "nova"} err := s.service.addFloatingIP(fip) c.Assert(err, gc.IsNil) defer s.service.removeFloatingIP(fip.Id) resp, err := s.authRequest("DELETE", "/os-floating-ips/1", nil, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusAccepted) _, err = s.service.floatingIP(fip.Id) c.Assert(err, gc.NotNil) } func (s *NovaHTTPSuite) TestAddServerFloatingIP(c *gc.C) { fip := nova.FloatingIP{Id: "1", IP: "1.2.3.4"} server := nova.ServerDetail{ Id: "sr1", Addresses: map[string][]nova.IPAddress{"private": []nova.IPAddress{}}, } err := s.service.addFloatingIP(fip) c.Assert(err, gc.IsNil) defer s.service.removeFloatingIP(fip.Id) err = s.service.addServer(server) c.Assert(err, gc.IsNil) defer s.service.removeServer(server.Id) c.Assert(s.service.hasServerFloatingIP(server.Id, fip.IP), gc.Equals, false) var req struct { AddFloatingIP struct { Address string `json:"address"` } `json:"addFloatingIp"` } req.AddFloatingIP.Address = fip.IP resp, err := s.jsonRequest("POST", "/servers/"+server.Id+"/action", req, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusAccepted) c.Assert(s.service.hasServerFloatingIP(server.Id, fip.IP), gc.Equals, true) err = s.service.removeServerFloatingIP(server.Id, fip.Id) c.Assert(err, gc.IsNil) } func (s *NovaHTTPSuite) TestRemoveServerFloatingIP(c *gc.C) { fip := nova.FloatingIP{Id: "1", IP: "1.2.3.4"} server := nova.ServerDetail{ Id: "sr1", Addresses: map[string][]nova.IPAddress{"private": []nova.IPAddress{}}, } err := s.service.addFloatingIP(fip) c.Assert(err, gc.IsNil) defer s.service.removeFloatingIP(fip.Id) err = s.service.addServer(server) c.Assert(err, gc.IsNil) defer s.service.removeServer(server.Id) err = s.service.addServerFloatingIP(server.Id, fip.Id) c.Assert(err, gc.IsNil) defer s.service.removeServerFloatingIP(server.Id, fip.Id) c.Assert(s.service.hasServerFloatingIP(server.Id, fip.IP), gc.Equals, true) var req struct { RemoveFloatingIP struct { Address string `json:"address"` } `json:"removeFloatingIp"` } req.RemoveFloatingIP.Address = fip.IP resp, err := s.jsonRequest("POST", "/servers/"+server.Id+"/action", req, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusAccepted) c.Assert(s.service.hasServerFloatingIP(server.Id, fip.IP), gc.Equals, false) } func (s *NovaHTTPSuite) TestListAvailabilityZones(c *gc.C) { resp, err := s.jsonRequest("GET", "/os-availability-zone", nil, nil) c.Assert(err, gc.IsNil) assertBody(c, resp, errNotFoundJSON) zones := []nova.AvailabilityZone{ {Name: "az1"}, { Name: "az2", State: nova.AvailabilityZoneState{Available: true}, }, } s.service.SetAvailabilityZones(zones...) resp, err = s.jsonRequest("GET", "/os-availability-zone", nil, nil) c.Assert(err, gc.IsNil) var expected struct { Zones []nova.AvailabilityZone `json:"availabilityZoneInfo"` } assertJSON(c, resp, &expected) c.Assert(expected.Zones, gc.DeepEquals, zones) } func (s *NovaHTTPSSuite) SetUpSuite(c *gc.C) { s.HTTPSuite.SetUpSuite(c) identityDouble := identityservice.NewUserPass() userInfo := identityDouble.AddUser("fred", "secret", "tenant", "default") s.token = userInfo.Token c.Assert(s.Server.URL[:8], gc.Equals, "https://") s.service = New(s.Server.URL, versionPath, userInfo.TenantId, region, identityDouble, nil) if s.useNeutronNetworking { c.Logf("Nova Service using Neutron Networking") s.service.AddNeutronModel(neutronmodel.New()) } else { c.Logf("Nova Service using Nova Networking") } } func (s *NovaHTTPSSuite) TearDownSuite(c *gc.C) { s.HTTPSuite.TearDownSuite(c) } func (s *NovaHTTPSSuite) SetUpTest(c *gc.C) { s.HTTPSuite.SetUpTest(c) s.service.SetupHTTP(s.Mux) } func (s *NovaHTTPSSuite) TearDownTest(c *gc.C) { s.HTTPSuite.TearDownTest(c) } func (s *NovaHTTPSSuite) TestHasHTTPSServiceURL(c *gc.C) { endpoints := s.service.Endpoints() c.Assert(endpoints[0].PublicURL[:8], gc.Equals, "https://") } func (s *NovaHTTPSuite) TestSetServerMetadata(c *gc.C) { const serverId = "sr1" err := s.service.addServer(nova.ServerDetail{Id: serverId}) c.Assert(err, gc.IsNil) defer s.service.removeServer(serverId) var req struct { Metadata map[string]string `json:"metadata"` } req.Metadata = map[string]string{ "k1": "v1", "k2": "v2", } resp, err := s.jsonRequest("POST", "/servers/"+serverId+"/metadata", req, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) server, err := s.service.server(serverId) c.Assert(err, gc.IsNil) c.Assert(server.Metadata, gc.DeepEquals, req.Metadata) } func (s *NovaHTTPSuite) TestAttachVolumeBlankDeviceName(c *gc.C) { var req struct { VolumeAttachment struct { Device string `json:"device"` } `json:"volumeAttachment"` } resp, err := s.jsonRequest("POST", "/servers/123/os-volume_attachments", req, nil) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, http.StatusBadRequest) // Passing an empty string in the "device" attribute // is invalid. It should be omitted instead. message := "Invalid input for field/attribute device. Value: '' does not match '(^/dev/x{0,1}[a-z]{0,1}d{0,1})([a-z]+)[0-9]*$'" assertBody(c, resp, &errorResponse{ http.StatusBadRequest, fmt.Sprintf(`{"badRequest": {"message": "%s", "code": 400}}`, message), "application/json; charset=UTF-8", message, nil, nil, }) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/novaservice/service_test.go000066400000000000000000001216011414605405700302120ustar00rootroot00000000000000// Nova double testing service - internal direct API tests package novaservice import ( "fmt" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/nova" "gopkg.in/goose.v1/testservices/hook" "gopkg.in/goose.v1/testservices/neutronmodel" ) type NovaSuite struct { service *Nova useNeutronNetworking bool } const ( versionPath = "v2" hostname = "http://example.com" region = "region" ) var _ = gc.Suite(&NovaSuite{useNeutronNetworking: true}) var _ = gc.Suite(&NovaSuite{useNeutronNetworking: false}) func (s *NovaSuite) SetUpSuite(c *gc.C) { s.service = New(hostname, versionPath, "tenant", region, nil, nil) if s.useNeutronNetworking { c.Logf("Nova Service using Neutron Networking") s.service.AddNeutronModel(neutronmodel.New()) } else { c.Logf("Nova Service using Nova Networking") } } func (s *NovaSuite) ensureNoFlavor(c *gc.C, flavor nova.FlavorDetail) { _, err := s.service.flavor(flavor.Id) c.Assert(err, gc.ErrorMatches, fmt.Sprintf("itemNotFound: No such flavor %q", flavor.Id)) } func (s *NovaSuite) ensureNoServer(c *gc.C, server nova.ServerDetail) { _, err := s.service.server(server.Id) c.Assert(err, gc.ErrorMatches, fmt.Sprintf("itemNotFound: No such server %q", server.Id)) } func (s *NovaSuite) ensureNoGroup(c *gc.C, group nova.SecurityGroup) { _, err := s.service.securityGroup(group.Id) c.Assert(err, gc.ErrorMatches, fmt.Sprintf("itemNotFound: No such security group %s", group.Id)) } func (s *NovaSuite) ensureNoRule(c *gc.C, rule nova.SecurityGroupRule) { _, err := s.service.securityGroupRule(rule.Id) c.Assert(err, gc.ErrorMatches, fmt.Sprintf("itemNotFound: No such security group rule %s", rule.Id)) } func (s *NovaSuite) ensureNoIP(c *gc.C, ip nova.FloatingIP) { _, err := s.service.floatingIP(ip.Id) c.Assert(err, gc.ErrorMatches, fmt.Sprintf("itemNotFound: No such floating IP %q", ip.Id)) } func (s *NovaSuite) createFlavor(c *gc.C, flavor nova.FlavorDetail) { s.ensureNoFlavor(c, flavor) err := s.service.addFlavor(flavor) c.Assert(err, gc.IsNil) } func (s *NovaSuite) createServer(c *gc.C, server nova.ServerDetail) { s.ensureNoServer(c, server) err := s.service.addServer(server) c.Assert(err, gc.IsNil) } func (s *NovaSuite) createGroup(c *gc.C, group nova.SecurityGroup) { s.ensureNoGroup(c, group) err := s.service.addSecurityGroup(group) c.Assert(err, gc.IsNil) } func (s *NovaSuite) createIP(c *gc.C, ip nova.FloatingIP) { s.ensureNoIP(c, ip) err := s.service.addFloatingIP(ip) c.Assert(err, gc.IsNil) } func (s *NovaSuite) deleteFlavor(c *gc.C, flavor nova.FlavorDetail) { err := s.service.removeFlavor(flavor.Id) c.Assert(err, gc.IsNil) s.ensureNoFlavor(c, flavor) } func (s *NovaSuite) deleteServer(c *gc.C, server nova.ServerDetail) { err := s.service.removeServer(server.Id) c.Assert(err, gc.IsNil) s.ensureNoServer(c, server) } func (s *NovaSuite) deleteGroup(c *gc.C, group nova.SecurityGroup) { err := s.service.removeSecurityGroup(group.Id) c.Assert(err, gc.IsNil) s.ensureNoGroup(c, group) } func (s *NovaSuite) deleteRule(c *gc.C, rule nova.SecurityGroupRule) { err := s.service.removeSecurityGroupRule(rule.Id) c.Assert(err, gc.IsNil) s.ensureNoRule(c, rule) } func (s *NovaSuite) deleteIP(c *gc.C, ip nova.FloatingIP) { err := s.service.removeFloatingIP(ip.Id) c.Assert(err, gc.IsNil) s.ensureNoIP(c, ip) } func (s *NovaSuite) TestAddRemoveFlavor(c *gc.C) { flavor := nova.FlavorDetail{Id: "test"} s.createFlavor(c, flavor) s.deleteFlavor(c, flavor) } func (s *NovaSuite) TestBuildLinksAndAddFlavor(c *gc.C) { flavor := nova.FlavorDetail{Id: "test"} s.service.buildFlavorLinks(&flavor) s.createFlavor(c, flavor) defer s.deleteFlavor(c, flavor) fl, _ := s.service.flavor(flavor.Id) url := "/flavors/" + flavor.Id links := []nova.Link{ {Href: s.service.endpointURL(true, url), Rel: "self"}, {Href: s.service.endpointURL(false, url), Rel: "bookmark"}, } c.Assert(fl.Links, gc.DeepEquals, links) } func (s *NovaSuite) TestAddFlavorWithLinks(c *gc.C) { flavor := nova.FlavorDetail{ Id: "test", Links: []nova.Link{ {Href: "href", Rel: "rel"}, }, } s.createFlavor(c, flavor) defer s.deleteFlavor(c, flavor) fl, _ := s.service.flavor(flavor.Id) c.Assert(*fl, gc.DeepEquals, flavor) } func (s *NovaSuite) TestAddFlavorTwiceFails(c *gc.C) { flavor := nova.FlavorDetail{Id: "test"} s.createFlavor(c, flavor) defer s.deleteFlavor(c, flavor) err := s.service.addFlavor(flavor) c.Assert(err, gc.ErrorMatches, `conflictingRequest: A flavor with id "test" already exists`) } func (s *NovaSuite) TestRemoveFlavorTwiceFails(c *gc.C) { flavor := nova.FlavorDetail{Id: "test"} s.createFlavor(c, flavor) s.deleteFlavor(c, flavor) err := s.service.removeFlavor(flavor.Id) c.Assert(err, gc.ErrorMatches, `itemNotFound: No such flavor "test"`) } func (s *NovaSuite) TestAllFlavors(c *gc.C) { // The test service has 2 default flavours. flavors := s.service.allFlavors() c.Assert(flavors, gc.HasLen, 3) for _, fl := range flavors { c.Assert(fl.Name == "m1.tiny" || fl.Name == "m1.small" || fl.Name == "m1.medium", gc.Equals, true) } } func (s *NovaSuite) TestAllFlavorsAsEntities(c *gc.C) { // The test service has 2 default flavours. entities := s.service.allFlavorsAsEntities() c.Assert(entities, gc.HasLen, 3) for _, fl := range entities { c.Assert(fl.Name == "m1.tiny" || fl.Name == "m1.small" || fl.Name == "m1.medium", gc.Equals, true) } } func (s *NovaSuite) TestGetFlavor(c *gc.C) { flavor := nova.FlavorDetail{ Id: "test", Name: "flavor", RAM: 128, VCPUs: 2, Disk: 123, } s.createFlavor(c, flavor) defer s.deleteFlavor(c, flavor) fl, _ := s.service.flavor(flavor.Id) c.Assert(*fl, gc.DeepEquals, flavor) } func (s *NovaSuite) TestGetFlavorAsEntity(c *gc.C) { entity := nova.Entity{ Id: "test", Name: "flavor", } flavor := nova.FlavorDetail{ Id: entity.Id, Name: entity.Name, } s.createFlavor(c, flavor) defer s.deleteFlavor(c, flavor) ent, _ := s.service.flavorAsEntity(flavor.Id) c.Assert(*ent, gc.DeepEquals, entity) } func (s *NovaSuite) TestAddRemoveServer(c *gc.C) { server := nova.ServerDetail{Id: "test"} s.createServer(c, server) s.deleteServer(c, server) } func (s *NovaSuite) TestBuildLinksAndAddServer(c *gc.C) { server := nova.ServerDetail{Id: "test"} s.service.buildServerLinks(&server) s.createServer(c, server) defer s.deleteServer(c, server) sr, _ := s.service.server(server.Id) url := "/servers/" + server.Id links := []nova.Link{ {Href: s.service.endpointURL(true, url), Rel: "self"}, {Href: s.service.endpointURL(false, url), Rel: "bookmark"}, } c.Assert(sr.Links, gc.DeepEquals, links) } func (s *NovaSuite) TestAddServerWithLinks(c *gc.C) { server := nova.ServerDetail{ Id: "test", Links: []nova.Link{ {Href: "href", Rel: "rel"}, }, } s.createServer(c, server) defer s.deleteServer(c, server) sr, _ := s.service.server(server.Id) c.Assert(*sr, gc.DeepEquals, server) } func (s *NovaSuite) TestAddServerTwiceFails(c *gc.C) { server := nova.ServerDetail{Id: "test"} s.createServer(c, server) defer s.deleteServer(c, server) err := s.service.addServer(server) c.Assert(err, gc.ErrorMatches, `conflictingRequest: A server with id "test" already exists`) } // A control point can be used to change the status of the added server. func (s *NovaSuite) TestAddServerControlPoint(c *gc.C) { cleanup := s.service.RegisterControlPoint( "addServer", func(sc hook.ServiceControl, args ...interface{}) error { details := args[0].(*nova.ServerDetail) details.Status = nova.StatusBuildSpawning return nil }, ) defer cleanup() server := &nova.ServerDetail{ Id: "test", Status: nova.StatusActive, } s.createServer(c, *server) defer s.deleteServer(c, *server) server, _ = s.service.server(server.Id) c.Assert(server.Status, gc.Equals, nova.StatusBuildSpawning) } func (s *NovaSuite) TestRemoveServerTwiceFails(c *gc.C) { server := nova.ServerDetail{Id: "test"} s.createServer(c, server) s.deleteServer(c, server) err := s.service.removeServer(server.Id) c.Assert(err, gc.ErrorMatches, `itemNotFound: No such server "test"`) } func (s *NovaSuite) TestAllServers(c *gc.C) { servers, err := s.service.allServers(nil) c.Assert(err, gc.IsNil) c.Assert(servers, gc.HasLen, 0) servers = []nova.ServerDetail{ {Id: "sr1"}, {Id: "sr2"}, } s.createServer(c, servers[0]) defer s.deleteServer(c, servers[1]) s.createServer(c, servers[1]) defer s.deleteServer(c, servers[0]) sr, err := s.service.allServers(nil) c.Assert(err, gc.IsNil) c.Assert(sr, gc.HasLen, len(servers)) if sr[0].Id != servers[0].Id { sr[0], sr[1] = sr[1], sr[0] } c.Assert(sr, gc.DeepEquals, servers) } func (s *NovaSuite) TestAllServersWithFilters(c *gc.C) { servers, err := s.service.allServers(nil) c.Assert(err, gc.IsNil) c.Assert(servers, gc.HasLen, 0) servers = []nova.ServerDetail{ {Id: "sr1", Name: "test", Status: nova.StatusActive}, {Id: "sr2", Name: "other", Status: nova.StatusBuild}, {Id: "sr3", Name: "foo", Status: nova.StatusRescue}, } for _, server := range servers { s.createServer(c, server) defer s.deleteServer(c, server) } f := filter{ nova.FilterStatus: nova.StatusRescue, } sr, err := s.service.allServers(f) c.Assert(err, gc.IsNil) c.Assert(sr, gc.HasLen, 1) c.Assert(sr[0], gc.DeepEquals, servers[2]) f[nova.FilterStatus] = nova.StatusBuild sr, err = s.service.allServers(f) c.Assert(err, gc.IsNil) c.Assert(sr, gc.HasLen, 1) c.Assert(sr[0], gc.DeepEquals, servers[1]) f = filter{ nova.FilterServer: "test", } sr, err = s.service.allServers(f) c.Assert(err, gc.IsNil) c.Assert(sr, gc.HasLen, 1) c.Assert(sr[0], gc.DeepEquals, servers[0]) f[nova.FilterServer] = "other" sr, err = s.service.allServers(f) c.Assert(err, gc.IsNil) c.Assert(sr, gc.HasLen, 1) c.Assert(sr[0], gc.DeepEquals, servers[1]) f[nova.FilterServer] = "foo" f[nova.FilterStatus] = nova.StatusRescue sr, err = s.service.allServers(f) c.Assert(err, gc.IsNil) c.Assert(sr, gc.HasLen, 1) c.Assert(sr[0], gc.DeepEquals, servers[2]) } func (s *NovaSuite) TestAllServersWithEmptyFilter(c *gc.C) { servers, err := s.service.allServers(nil) c.Assert(err, gc.IsNil) c.Assert(servers, gc.HasLen, 0) servers = []nova.ServerDetail{ {Id: "sr1", Name: "srv1"}, {Id: "sr2", Name: "srv2"}, } for _, server := range servers { s.createServer(c, server) defer s.deleteServer(c, server) } sr, err := s.service.allServers(nil) c.Assert(err, gc.IsNil) c.Assert(sr, gc.HasLen, 2) if sr[0].Id != servers[0].Id { sr[0], sr[1] = sr[1], sr[0] } c.Assert(sr, gc.DeepEquals, servers) } func (s *NovaSuite) TestAllServersWithRegexFilters(c *gc.C) { servers, err := s.service.allServers(nil) c.Assert(err, gc.IsNil) c.Assert(servers, gc.HasLen, 0) servers = []nova.ServerDetail{ {Id: "sr1", Name: "foobarbaz"}, {Id: "sr2", Name: "foo123baz"}, {Id: "sr3", Name: "123barbaz"}, {Id: "sr4", Name: "foobar123"}, } for _, server := range servers { s.createServer(c, server) defer s.deleteServer(c, server) } f := filter{ nova.FilterServer: `foo.*baz`, } sr, err := s.service.allServers(f) c.Assert(err, gc.IsNil) c.Assert(sr, gc.HasLen, 2) if sr[0].Id != servers[0].Id { sr[0], sr[1] = sr[1], sr[0] } c.Assert(sr, gc.DeepEquals, servers[:2]) f[nova.FilterServer] = `^[a-z]+[0-9]+` sr, err = s.service.allServers(f) c.Assert(err, gc.IsNil) c.Assert(sr, gc.HasLen, 2) if sr[0].Id != servers[1].Id { sr[0], sr[1] = sr[1], sr[0] } c.Assert(sr[0], gc.DeepEquals, servers[1]) c.Assert(sr[1], gc.DeepEquals, servers[3]) } func (s *NovaSuite) TestAllServersWithMultipleFilters(c *gc.C) { servers, err := s.service.allServers(nil) c.Assert(err, gc.IsNil) c.Assert(servers, gc.HasLen, 0) servers = []nova.ServerDetail{ {Id: "sr1", Name: "s1", Status: nova.StatusActive}, {Id: "sr2", Name: "s2", Status: nova.StatusActive}, {Id: "sr3", Name: "s3", Status: nova.StatusActive}, } for _, server := range servers { s.createServer(c, server) defer s.deleteServer(c, server) } f := filter{ nova.FilterStatus: nova.StatusActive, nova.FilterServer: `.*2.*`, } sr, err := s.service.allServers(f) c.Assert(err, gc.IsNil) c.Assert(sr, gc.HasLen, 1) c.Assert(sr[0], gc.DeepEquals, servers[1]) f[nova.FilterStatus] = nova.StatusBuild sr, err = s.service.allServers(f) c.Assert(err, gc.IsNil) c.Assert(sr, gc.HasLen, 0) f[nova.FilterStatus] = nova.StatusPassword f[nova.FilterServer] = `.*[23].*` sr, err = s.service.allServers(f) c.Assert(err, gc.IsNil) c.Assert(sr, gc.HasLen, 0) f[nova.FilterStatus] = nova.StatusActive sr, err = s.service.allServers(f) c.Assert(err, gc.IsNil) c.Assert(sr, gc.HasLen, 2) if sr[0].Id != servers[1].Id { sr[0], sr[1] = sr[1], sr[0] } c.Assert(sr, gc.DeepEquals, servers[1:]) } func (s *NovaSuite) TestAllServersAsEntities(c *gc.C) { entities, err := s.service.allServersAsEntities(nil) c.Assert(err, gc.IsNil) c.Assert(entities, gc.HasLen, 0) entities = []nova.Entity{ {Id: "sr1"}, {Id: "sr2"}, } servers := []nova.ServerDetail{ {Id: entities[0].Id}, {Id: entities[1].Id}, } s.createServer(c, servers[0]) defer s.deleteServer(c, servers[0]) s.createServer(c, servers[1]) defer s.deleteServer(c, servers[1]) ent, err := s.service.allServersAsEntities(nil) c.Assert(err, gc.IsNil) c.Assert(ent, gc.HasLen, len(entities)) if ent[0].Id != entities[0].Id { ent[0], ent[1] = ent[1], ent[0] } c.Assert(ent, gc.DeepEquals, entities) } func (s *NovaSuite) TestAllServersAsEntitiesWithFilters(c *gc.C) { servers, err := s.service.allServers(nil) c.Assert(err, gc.IsNil) c.Assert(servers, gc.HasLen, 0) servers = []nova.ServerDetail{ {Id: "sr1", Name: "test", Status: nova.StatusActive}, {Id: "sr2", Name: "other", Status: nova.StatusBuild}, {Id: "sr3", Name: "foo", Status: nova.StatusRescue}, } entities := []nova.Entity{} for _, server := range servers { s.createServer(c, server) defer s.deleteServer(c, server) entities = append(entities, nova.Entity{ Id: server.Id, Name: server.Name, Links: server.Links, }) } f := filter{ nova.FilterStatus: nova.StatusRescue, } ent, err := s.service.allServersAsEntities(f) c.Assert(err, gc.IsNil) c.Assert(ent, gc.HasLen, 1) c.Assert(ent[0], gc.DeepEquals, entities[2]) f[nova.FilterStatus] = nova.StatusBuild ent, err = s.service.allServersAsEntities(f) c.Assert(err, gc.IsNil) c.Assert(ent, gc.HasLen, 1) c.Assert(ent[0], gc.DeepEquals, entities[1]) f = filter{ nova.FilterServer: "test", } ent, err = s.service.allServersAsEntities(f) c.Assert(err, gc.IsNil) c.Assert(ent, gc.HasLen, 1) c.Assert(ent[0], gc.DeepEquals, entities[0]) f[nova.FilterServer] = "other" ent, err = s.service.allServersAsEntities(f) c.Assert(err, gc.IsNil) c.Assert(ent, gc.HasLen, 1) c.Assert(ent[0], gc.DeepEquals, entities[1]) f[nova.FilterServer] = "foo" f[nova.FilterStatus] = nova.StatusRescue ent, err = s.service.allServersAsEntities(f) c.Assert(err, gc.IsNil) c.Assert(ent, gc.HasLen, 1) c.Assert(ent[0], gc.DeepEquals, entities[2]) } func (s *NovaSuite) TestGetServer(c *gc.C) { server := nova.ServerDetail{ Id: "test", Name: "server", AddressIPv4: "1.2.3.4", AddressIPv6: "1::fff", Created: "1/1/1", Flavor: nova.Entity{Id: "fl1", Name: "flavor1"}, Image: nova.Entity{Id: "im1", Name: "image1"}, HostId: "myhost", Progress: 123, Status: "st", TenantId: "tenant", Updated: "2/3/4", UserId: "user", } s.createServer(c, server) defer s.deleteServer(c, server) sr, _ := s.service.server(server.Id) c.Assert(*sr, gc.DeepEquals, server) } func (s *NovaSuite) TestGetServerAsEntity(c *gc.C) { entity := nova.Entity{ Id: "test", Name: "server", } server := nova.ServerDetail{ Id: entity.Id, Name: entity.Name, } s.createServer(c, server) defer s.deleteServer(c, server) ent, _ := s.service.serverAsEntity(server.Id) c.Assert(*ent, gc.DeepEquals, entity) } func (s *NovaSuite) TestGetServerByName(c *gc.C) { named, err := s.service.serverByName("test") c.Assert(err, gc.ErrorMatches, `itemNotFound: No such server named "test"`) servers := []nova.ServerDetail{ {Id: "sr1", Name: "test"}, {Id: "sr2", Name: "test"}, {Id: "sr3", Name: "not test"}, } for _, server := range servers { s.createServer(c, server) defer s.deleteServer(c, server) } named, err = s.service.serverByName("test") c.Assert(err, gc.IsNil) // order is not guaranteed, so check both possible results if named.Id == servers[0].Id { c.Assert(*named, gc.DeepEquals, servers[0]) } else { c.Assert(*named, gc.DeepEquals, servers[1]) } } func (s *NovaSuite) TestAddRemoveSecurityGroup(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } group := nova.SecurityGroup{Id: "1"} s.createGroup(c, group) s.deleteGroup(c, group) } func (s *NovaSuite) TestAddSecurityGroupWithRules(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } group := nova.SecurityGroup{ Id: "1", Name: "test", TenantId: s.service.TenantId, Rules: []nova.SecurityGroupRule{ {Id: "10", ParentGroupId: "1"}, {Id: "20", ParentGroupId: "1"}, }, } s.createGroup(c, group) defer s.deleteGroup(c, group) gr, _ := s.service.securityGroup(group.Id) c.Assert(*gr, gc.DeepEquals, group) } func (s *NovaSuite) TestAddSecurityGroupTwiceFails(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } group := nova.SecurityGroup{Id: "1", Name: "test"} s.createGroup(c, group) defer s.deleteGroup(c, group) err := s.service.addSecurityGroup(group) c.Assert(err, gc.ErrorMatches, "conflictingRequest: A security group with id 1 already exists") } func (s *NovaSuite) TestRemoveSecurityGroupTwiceFails(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } group := nova.SecurityGroup{Id: "1", Name: "test"} s.createGroup(c, group) s.deleteGroup(c, group) err := s.service.removeSecurityGroup(group.Id) c.Assert(err, gc.ErrorMatches, "itemNotFound: No such security group 1") } func (s *NovaSuite) TestAllSecurityGroups(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } groups := s.service.allSecurityGroups() // There is always a default security group. c.Assert(groups, gc.HasLen, 1) groups = []nova.SecurityGroup{ { Id: "1", Name: "one", TenantId: s.service.TenantId, Rules: []nova.SecurityGroupRule{}, }, { Id: "2", Name: "two", TenantId: s.service.TenantId, Rules: []nova.SecurityGroupRule{}, }, } s.createGroup(c, groups[0]) defer s.deleteGroup(c, groups[0]) s.createGroup(c, groups[1]) defer s.deleteGroup(c, groups[1]) gr := s.service.allSecurityGroups() c.Assert(gr, gc.HasLen, len(groups)+1) checkGroupsInList(c, groups, gr) } func (s *NovaSuite) TestGetSecurityGroup(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } group := nova.SecurityGroup{ Id: "42", TenantId: s.service.TenantId, Name: "group", Description: "desc", Rules: []nova.SecurityGroupRule{}, } s.createGroup(c, group) defer s.deleteGroup(c, group) gr, _ := s.service.securityGroup(group.Id) c.Assert(*gr, gc.DeepEquals, group) } func (s *NovaSuite) TestGetSecurityGroupByName(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } group := nova.SecurityGroup{ Id: "1", Name: "test", TenantId: s.service.TenantId, Rules: []nova.SecurityGroupRule{}, } s.ensureNoGroup(c, group) gr, err := s.service.securityGroupByName(group.Name) c.Assert(err, gc.ErrorMatches, `itemNotFound: No such security group named "test"`) s.createGroup(c, group) defer s.deleteGroup(c, group) gr, err = s.service.securityGroupByName(group.Name) c.Assert(err, gc.IsNil) c.Assert(*gr, gc.DeepEquals, group) } func (s *NovaSuite) TestAddHasRemoveSecurityGroupRule(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } group := nova.SecurityGroup{Id: "1"} ri := nova.RuleInfo{ParentGroupId: group.Id, GroupId: &group.Id} rule := nova.SecurityGroupRule{Id: "10", ParentGroupId: group.Id} s.ensureNoGroup(c, group) s.ensureNoRule(c, rule) ok := s.service.hasSecurityGroupRule(group.Id, rule.Id) c.Assert(ok, gc.Equals, false) s.createGroup(c, group) err := s.service.addSecurityGroupRule(rule.Id, ri) c.Assert(err, gc.IsNil) ok = s.service.hasSecurityGroupRule(group.Id, rule.Id) c.Assert(ok, gc.Equals, true) s.deleteGroup(c, group) ok = s.service.hasSecurityGroupRule("-1", rule.Id) c.Assert(ok, gc.Equals, true) ok = s.service.hasSecurityGroupRule(group.Id, rule.Id) c.Assert(ok, gc.Equals, false) s.deleteRule(c, rule) ok = s.service.hasSecurityGroupRule("-1", rule.Id) c.Assert(ok, gc.Equals, false) } func (s *NovaSuite) TestAddGetIngressSecurityGroupRule(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } group := nova.SecurityGroup{Id: "1"} s.createGroup(c, group) defer s.deleteGroup(c, group) ri := nova.RuleInfo{ FromPort: 1234, ToPort: 4321, IPProtocol: "tcp", Cidr: "1.2.3.4/5", ParentGroupId: group.Id, } rule := nova.SecurityGroupRule{ Id: "10", ParentGroupId: group.Id, FromPort: &ri.FromPort, ToPort: &ri.ToPort, IPProtocol: &ri.IPProtocol, IPRange: make(map[string]string), } rule.IPRange["cidr"] = ri.Cidr s.ensureNoRule(c, rule) err := s.service.addSecurityGroupRule(rule.Id, ri) c.Assert(err, gc.IsNil) defer s.deleteRule(c, rule) ru, err := s.service.securityGroupRule(rule.Id) c.Assert(err, gc.IsNil) c.Assert(ru.Id, gc.Equals, rule.Id) c.Assert(ru.ParentGroupId, gc.Equals, rule.ParentGroupId) c.Assert(*ru.FromPort, gc.Equals, *rule.FromPort) c.Assert(*ru.ToPort, gc.Equals, *rule.ToPort) c.Assert(*ru.IPProtocol, gc.Equals, *rule.IPProtocol) c.Assert(ru.IPRange, gc.DeepEquals, rule.IPRange) } func (s *NovaSuite) TestAddGetGroupSecurityGroupRule(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } srcGroup := nova.SecurityGroup{Id: "1", Name: "source", TenantId: s.service.TenantId} tgtGroup := nova.SecurityGroup{Id: "2", Name: "target"} s.createGroup(c, srcGroup) defer s.deleteGroup(c, srcGroup) s.createGroup(c, tgtGroup) defer s.deleteGroup(c, tgtGroup) ri := nova.RuleInfo{ FromPort: 1234, ToPort: 4321, IPProtocol: "tcp", GroupId: &srcGroup.Id, ParentGroupId: tgtGroup.Id, } rule := nova.SecurityGroupRule{ Id: "10", ParentGroupId: tgtGroup.Id, FromPort: &ri.FromPort, ToPort: &ri.ToPort, IPProtocol: &ri.IPProtocol, Group: nova.SecurityGroupRef{ TenantId: srcGroup.TenantId, Name: srcGroup.Name, }, } s.ensureNoRule(c, rule) err := s.service.addSecurityGroupRule(rule.Id, ri) c.Assert(err, gc.IsNil) defer s.deleteRule(c, rule) ru, err := s.service.securityGroupRule(rule.Id) c.Assert(err, gc.IsNil) c.Assert(ru.Id, gc.Equals, rule.Id) c.Assert(ru.ParentGroupId, gc.Equals, rule.ParentGroupId) c.Assert(*ru.FromPort, gc.Equals, *rule.FromPort) c.Assert(*ru.ToPort, gc.Equals, *rule.ToPort) c.Assert(*ru.IPProtocol, gc.Equals, *rule.IPProtocol) c.Assert(ru.Group, gc.DeepEquals, rule.Group) } func (s *NovaSuite) TestAddSecurityGroupRuleTwiceFails(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } group := nova.SecurityGroup{Id: "1"} s.createGroup(c, group) defer s.deleteGroup(c, group) ri := nova.RuleInfo{ParentGroupId: group.Id, GroupId: &group.Id} rule := nova.SecurityGroupRule{Id: "10"} s.ensureNoRule(c, rule) err := s.service.addSecurityGroupRule(rule.Id, ri) c.Assert(err, gc.IsNil) defer s.deleteRule(c, rule) err = s.service.addSecurityGroupRule(rule.Id, ri) c.Assert(err, gc.ErrorMatches, "conflictingRequest: A security group rule with id 10 already exists") } func (s *NovaSuite) TestAddSecurityGroupRuleToParentTwiceFails(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } group := nova.SecurityGroup{ Id: "1", Rules: []nova.SecurityGroupRule{ {Id: "10"}, }, } s.createGroup(c, group) defer s.deleteGroup(c, group) ri := nova.RuleInfo{ParentGroupId: group.Id} rule := nova.SecurityGroupRule{Id: "10"} err := s.service.addSecurityGroupRule(rule.Id, ri) c.Assert(err, gc.ErrorMatches, "conflictingRequest: Cannot add twice rule 10 to security group 1") } func (s *NovaSuite) TestAddSecurityGroupRuleWithInvalidParentFails(c *gc.C) { invalidGroup := nova.SecurityGroup{Id: "1"} s.ensureNoGroup(c, invalidGroup) ri := nova.RuleInfo{ParentGroupId: invalidGroup.Id} rule := nova.SecurityGroupRule{Id: "10"} s.ensureNoRule(c, rule) err := s.service.addSecurityGroupRule(rule.Id, ri) c.Assert(err, gc.ErrorMatches, "itemNotFound: No such security group 1") } func (s *NovaSuite) TestAddGroupSecurityGroupRuleWithInvalidSourceFails(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } group := nova.SecurityGroup{Id: "1"} s.createGroup(c, group) defer s.deleteGroup(c, group) invalidGroupId := "42" ri := nova.RuleInfo{ ParentGroupId: group.Id, GroupId: &invalidGroupId, } rule := nova.SecurityGroupRule{Id: "10"} s.ensureNoRule(c, rule) err := s.service.addSecurityGroupRule(rule.Id, ri) c.Assert(err, gc.ErrorMatches, "conflictingRequest: Unknown source security group 42") } func (s *NovaSuite) TestAddSecurityGroupRuleUpdatesParent(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } group := nova.SecurityGroup{ Id: "1", Name: "test", TenantId: s.service.TenantId, } s.createGroup(c, group) defer s.deleteGroup(c, group) ri := nova.RuleInfo{ParentGroupId: group.Id, GroupId: &group.Id} rule := nova.SecurityGroupRule{ Id: "10", ParentGroupId: group.Id, Group: nova.SecurityGroupRef{TenantId: s.service.TenantId, Name: "test"}, } s.ensureNoRule(c, rule) err := s.service.addSecurityGroupRule(rule.Id, ri) c.Assert(err, gc.IsNil) defer s.deleteRule(c, rule) group.Rules = []nova.SecurityGroupRule{rule} gr, err := s.service.securityGroup(group.Id) c.Assert(err, gc.IsNil) c.Assert(*gr, gc.DeepEquals, group) } func (s *NovaSuite) TestAddSecurityGroupRuleKeepsNegativePort(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } group := nova.SecurityGroup{ Id: "1", Name: "test", TenantId: s.service.TenantId, } s.createGroup(c, group) defer s.deleteGroup(c, group) ri := nova.RuleInfo{ IPProtocol: "icmp", FromPort: -1, ToPort: -1, Cidr: "0.0.0.0/0", ParentGroupId: group.Id, } rule := nova.SecurityGroupRule{ Id: "10", ParentGroupId: group.Id, FromPort: &ri.FromPort, ToPort: &ri.ToPort, IPProtocol: &ri.IPProtocol, IPRange: map[string]string{"cidr": "0.0.0.0/0"}, } s.ensureNoRule(c, rule) err := s.service.addSecurityGroupRule(rule.Id, ri) c.Assert(err, gc.IsNil) defer s.deleteRule(c, rule) returnedGroup, err := s.service.securityGroup(group.Id) c.Assert(err, gc.IsNil) c.Assert(returnedGroup.Rules, gc.DeepEquals, []nova.SecurityGroupRule{rule}) } func (s *NovaSuite) TestRemoveSecurityGroupRuleTwiceFails(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } group := nova.SecurityGroup{Id: "1"} s.createGroup(c, group) defer s.deleteGroup(c, group) ri := nova.RuleInfo{ParentGroupId: group.Id, GroupId: &group.Id} rule := nova.SecurityGroupRule{Id: "10"} s.ensureNoRule(c, rule) err := s.service.addSecurityGroupRule(rule.Id, ri) c.Assert(err, gc.IsNil) s.deleteRule(c, rule) err = s.service.removeSecurityGroupRule(rule.Id) c.Assert(err, gc.ErrorMatches, "itemNotFound: No such security group rule 10") } func (s *NovaSuite) TestAddHasRemoveServerSecurityGroup(c *gc.C) { server := nova.ServerDetail{Id: "sr1"} group := nova.SecurityGroup{Id: "1"} s.ensureNoServer(c, server) s.ensureNoGroup(c, group) ok := s.service.hasServerSecurityGroup(server.Id, group.Id) c.Assert(ok, gc.Equals, false) s.createServer(c, server) defer s.deleteServer(c, server) ok = s.service.hasServerSecurityGroup(server.Id, group.Id) c.Assert(ok, gc.Equals, false) s.createGroup(c, group) defer s.deleteGroup(c, group) ok = s.service.hasServerSecurityGroup(server.Id, group.Id) c.Assert(ok, gc.Equals, false) err := s.service.addServerSecurityGroup(server.Id, group.Id) c.Assert(err, gc.IsNil) ok = s.service.hasServerSecurityGroup(server.Id, group.Id) c.Assert(ok, gc.Equals, true) err = s.service.removeServerSecurityGroup(server.Id, group.Id) c.Assert(err, gc.IsNil) ok = s.service.hasServerSecurityGroup(server.Id, group.Id) c.Assert(ok, gc.Equals, false) } func (s *NovaSuite) TestAddServerSecurityGroupWithInvalidServerFails(c *gc.C) { server := nova.ServerDetail{Id: "sr1"} group := nova.SecurityGroup{Id: "1"} s.ensureNoServer(c, server) s.createGroup(c, group) defer s.deleteGroup(c, group) err := s.service.addServerSecurityGroup(server.Id, group.Id) c.Assert(err, gc.ErrorMatches, `itemNotFound: No such server "sr1"`) } func (s *NovaSuite) TestAddServerSecurityGroupWithInvalidGroupFails(c *gc.C) { group := nova.SecurityGroup{Id: "1"} server := nova.ServerDetail{Id: "sr1"} s.ensureNoGroup(c, group) s.createServer(c, server) defer s.deleteServer(c, server) err := s.service.addServerSecurityGroup(server.Id, group.Id) c.Assert(err, gc.ErrorMatches, "itemNotFound: No such security group 1") } func (s *NovaSuite) TestAddServerSecurityGroupTwiceFails(c *gc.C) { server := nova.ServerDetail{Id: "sr1"} group := nova.SecurityGroup{Id: "1"} s.createServer(c, server) defer s.deleteServer(c, server) s.createGroup(c, group) defer s.deleteGroup(c, group) err := s.service.addServerSecurityGroup(server.Id, group.Id) c.Assert(err, gc.IsNil) err = s.service.addServerSecurityGroup(server.Id, group.Id) c.Assert(err, gc.ErrorMatches, `conflictingRequest: Server "sr1" already belongs to group 1`) err = s.service.removeServerSecurityGroup(server.Id, group.Id) c.Assert(err, gc.IsNil) } func (s *NovaSuite) TestAllServerSecurityGroups(c *gc.C) { server := nova.ServerDetail{Id: "sr1"} srvGroups := s.service.allServerSecurityGroups(server.Id) c.Assert(srvGroups, gc.HasLen, 0) s.createServer(c, server) defer s.deleteServer(c, server) groups := []nova.SecurityGroup{ { Id: "1", Name: "gr1", TenantId: s.service.TenantId, Rules: []nova.SecurityGroupRule{}, }, { Id: "2", Name: "gr2", TenantId: s.service.TenantId, Rules: []nova.SecurityGroupRule{}, }, } for _, group := range groups { s.createGroup(c, group) defer s.deleteGroup(c, group) err := s.service.addServerSecurityGroup(server.Id, group.Id) defer s.service.removeServerSecurityGroup(server.Id, group.Id) c.Assert(err, gc.IsNil) } srvGroups = s.service.allServerSecurityGroups(server.Id) c.Assert(srvGroups, gc.HasLen, len(groups)) if srvGroups[0].Id != groups[0].Id { srvGroups[0], srvGroups[1] = srvGroups[1], srvGroups[0] } // nova networking doesn't know about neutron egress direction rules, // created by default with a new security group if s.service.useNeutronNetworking { srvGroups[0].Rules = []nova.SecurityGroupRule{} srvGroups[1].Rules = []nova.SecurityGroupRule{} } c.Assert(srvGroups, gc.DeepEquals, groups) } func (s *NovaSuite) TestRemoveServerSecurityGroupWithInvalidServerFails(c *gc.C) { server := nova.ServerDetail{Id: "sr1"} group := nova.SecurityGroup{Id: "1"} s.createServer(c, server) s.createGroup(c, group) defer s.deleteGroup(c, group) err := s.service.addServerSecurityGroup(server.Id, group.Id) c.Assert(err, gc.IsNil) s.deleteServer(c, server) err = s.service.removeServerSecurityGroup(server.Id, group.Id) c.Assert(err, gc.ErrorMatches, `itemNotFound: No such server "sr1"`) s.createServer(c, server) defer s.deleteServer(c, server) err = s.service.removeServerSecurityGroup(server.Id, group.Id) c.Assert(err, gc.IsNil) } func (s *NovaSuite) TestRemoveServerSecurityGroupWithInvalidGroupFails(c *gc.C) { group := nova.SecurityGroup{Id: "1"} server := nova.ServerDetail{Id: "sr1"} s.createGroup(c, group) s.createServer(c, server) defer s.deleteServer(c, server) err := s.service.addServerSecurityGroup(server.Id, group.Id) c.Assert(err, gc.IsNil) s.deleteGroup(c, group) err = s.service.removeServerSecurityGroup(server.Id, group.Id) c.Assert(err, gc.ErrorMatches, "itemNotFound: No such security group 1") s.createGroup(c, group) defer s.deleteGroup(c, group) err = s.service.removeServerSecurityGroup(server.Id, group.Id) c.Assert(err, gc.IsNil) } func (s *NovaSuite) TestRemoveServerSecurityGroupTwiceFails(c *gc.C) { server := nova.ServerDetail{Id: "sr1"} group := nova.SecurityGroup{Id: "1"} s.createServer(c, server) defer s.deleteServer(c, server) s.createGroup(c, group) defer s.deleteGroup(c, group) err := s.service.addServerSecurityGroup(server.Id, group.Id) c.Assert(err, gc.IsNil) err = s.service.removeServerSecurityGroup(server.Id, group.Id) c.Assert(err, gc.IsNil) err = s.service.removeServerSecurityGroup(server.Id, group.Id) c.Assert(err, gc.ErrorMatches, `badRequest: Server "sr1" does not belong to group 1`) } func (s *NovaSuite) TestAddHasRemoveFloatingIP(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } ip := nova.FloatingIP{Id: "1", IP: "1.2.3.4"} s.ensureNoIP(c, ip) ok := s.service.hasFloatingIP(ip.IP) c.Assert(ok, gc.Equals, false) s.createIP(c, ip) ok = s.service.hasFloatingIP("invalid IP") c.Assert(ok, gc.Equals, false) ok = s.service.hasFloatingIP(ip.IP) c.Assert(ok, gc.Equals, true) s.deleteIP(c, ip) ok = s.service.hasFloatingIP(ip.IP) c.Assert(ok, gc.Equals, false) } func (s *NovaSuite) TestAddFloatingIPTwiceFails(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } ip := nova.FloatingIP{Id: "1"} s.createIP(c, ip) defer s.deleteIP(c, ip) err := s.service.addFloatingIP(ip) c.Assert(err, gc.ErrorMatches, "conflictingRequest: A floating IP with id 1 already exists") } func (s *NovaSuite) TestRemoveFloatingIPTwiceFails(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } ip := nova.FloatingIP{Id: "1"} s.createIP(c, ip) s.deleteIP(c, ip) err := s.service.removeFloatingIP(ip.Id) c.Assert(err, gc.ErrorMatches, "itemNotFound: No such floating IP \"1\"") } func (s *NovaSuite) TestAllFloatingIPs(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } fips := s.service.allFloatingIPs() c.Assert(fips, gc.HasLen, 0) fips = []nova.FloatingIP{ {Id: "1"}, {Id: "2"}, } s.createIP(c, fips[0]) defer s.deleteIP(c, fips[0]) s.createIP(c, fips[1]) defer s.deleteIP(c, fips[1]) ips := s.service.allFloatingIPs() c.Assert(ips, gc.HasLen, len(fips)) if ips[0].Id != fips[0].Id { ips[0], ips[1] = ips[1], ips[0] } c.Assert(ips, gc.DeepEquals, fips) } func (s *NovaSuite) TestGetFloatingIP(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } inst := "sr1" fixedIP := "4.3.2.1" fip := nova.FloatingIP{ Id: "1", IP: "1.2.3.4", Pool: "pool", InstanceId: &inst, FixedIP: &fixedIP, } s.createIP(c, fip) defer s.deleteIP(c, fip) ip, _ := s.service.floatingIP(fip.Id) c.Assert(*ip, gc.DeepEquals, fip) } func (s *NovaSuite) TestGetFloatingIPByAddr(c *gc.C) { if s.service.useNeutronNetworking { c.Skip("skipped in novaservice when using Neutron Model") } fip := nova.FloatingIP{Id: "1", IP: "1.2.3.4"} s.ensureNoIP(c, fip) ip, err := s.service.floatingIPByAddr(fip.IP) c.Assert(err, gc.NotNil) s.createIP(c, fip) defer s.deleteIP(c, fip) ip, err = s.service.floatingIPByAddr(fip.IP) c.Assert(err, gc.IsNil) c.Assert(*ip, gc.DeepEquals, fip) _, err = s.service.floatingIPByAddr("invalid") c.Assert(err, gc.ErrorMatches, `itemNotFound: No such floating IP "invalid"`) } func (s *NovaSuite) TestAddHasRemoveServerFloatingIP(c *gc.C) { server := nova.ServerDetail{ Id: "sr1", Addresses: map[string][]nova.IPAddress{"private": []nova.IPAddress{}}, } fip := nova.FloatingIP{Id: "1", IP: "1.2.3.4"} s.ensureNoServer(c, server) s.ensureNoIP(c, fip) ok := s.service.hasServerFloatingIP(server.Id, fip.IP) c.Assert(ok, gc.Equals, false) s.createServer(c, server) defer s.deleteServer(c, server) ok = s.service.hasServerFloatingIP(server.Id, fip.IP) c.Assert(ok, gc.Equals, false) s.createIP(c, fip) defer s.deleteIP(c, fip) ok = s.service.hasServerFloatingIP(server.Id, fip.IP) c.Assert(ok, gc.Equals, false) err := s.service.addServerFloatingIP(server.Id, fip.Id) c.Assert(err, gc.IsNil) ok = s.service.hasServerFloatingIP(server.Id, fip.IP) c.Assert(ok, gc.Equals, true) // Is the fip now listed with the server addresses? serverCopy, err := s.service.server(server.Id) c.Assert(err, gc.IsNil) var found bool for _, address := range serverCopy.Addresses["private"] { if address.Address == fip.IP { found = true } } if !found { c.Errorf("expected to find added fip in server addresses") } err = s.service.removeServerFloatingIP(server.Id, fip.Id) c.Assert(err, gc.IsNil) // Has the fip been removed from the listed server addresses? serverCopy, err = s.service.server(server.Id) c.Assert(err, gc.IsNil) found = false for _, address := range serverCopy.Addresses["private"] { if address.Address == fip.IP { found = true } } if found { c.Errorf("didn't expected to find removed fip in server addresses") } ok = s.service.hasServerFloatingIP(server.Id, fip.IP) c.Assert(ok, gc.Equals, false) } func (s *NovaSuite) TestAddServerFloatingIPWithInvalidServerFails(c *gc.C) { server := nova.ServerDetail{Id: "sr1"} fip := nova.FloatingIP{Id: "1"} s.ensureNoServer(c, server) s.createIP(c, fip) defer s.deleteIP(c, fip) err := s.service.addServerFloatingIP(server.Id, fip.Id) c.Assert(err, gc.ErrorMatches, `itemNotFound: No such server "sr1"`) } func (s *NovaSuite) TestAddServerFloatingIPWithInvalidIPFails(c *gc.C) { fip := nova.FloatingIP{Id: "1"} server := nova.ServerDetail{Id: "sr1"} s.ensureNoIP(c, fip) s.createServer(c, server) defer s.deleteServer(c, server) err := s.service.addServerFloatingIP(server.Id, fip.Id) c.Assert(err, gc.ErrorMatches, "itemNotFound: No such floating IP \"1\"") } func (s *NovaSuite) TestAddServerFloatingIPTwiceFails(c *gc.C) { server := nova.ServerDetail{ Id: "sr1", Addresses: map[string][]nova.IPAddress{"private": []nova.IPAddress{}}, } fip := nova.FloatingIP{Id: "1", IP: "1.2.3.4"} s.createServer(c, server) defer s.deleteServer(c, server) s.createIP(c, fip) defer s.deleteIP(c, fip) err := s.service.addServerFloatingIP(server.Id, fip.Id) c.Assert(err, gc.IsNil) err = s.service.addServerFloatingIP(server.Id, fip.Id) c.Assert(err, gc.ErrorMatches, `conflictingRequest: Server "sr1" already has floating IP 1`) err = s.service.removeServerFloatingIP(server.Id, fip.Id) c.Assert(err, gc.IsNil) } func (s *NovaSuite) TestRemoveServerFloatingIPWithInvalidServerFails(c *gc.C) { server := nova.ServerDetail{ Id: "sr1", Addresses: map[string][]nova.IPAddress{"private": []nova.IPAddress{}}, } fip := nova.FloatingIP{Id: "1", IP: "1.2.3.4"} s.createServer(c, server) s.createIP(c, fip) defer s.deleteIP(c, fip) err := s.service.addServerFloatingIP(server.Id, fip.Id) c.Assert(err, gc.IsNil) s.deleteServer(c, server) err = s.service.removeServerFloatingIP(server.Id, fip.Id) c.Assert(err, gc.ErrorMatches, `itemNotFound: No such server "sr1"`) s.createServer(c, server) defer s.deleteServer(c, server) err = s.service.removeServerFloatingIP(server.Id, fip.Id) c.Assert(err, gc.IsNil) } func (s *NovaSuite) TestRemoveServerFloatingIPWithInvalidIPFails(c *gc.C) { fip := nova.FloatingIP{Id: "1", IP: "1.2.3.4"} server := nova.ServerDetail{ Id: "sr1", Addresses: map[string][]nova.IPAddress{"private": []nova.IPAddress{}}, } s.createIP(c, fip) s.createServer(c, server) defer s.deleteServer(c, server) err := s.service.addServerFloatingIP(server.Id, fip.Id) c.Assert(err, gc.IsNil) s.deleteIP(c, fip) err = s.service.removeServerFloatingIP(server.Id, fip.Id) c.Assert(err, gc.ErrorMatches, "itemNotFound: No such floating IP \"1\"") s.createIP(c, fip) defer s.deleteIP(c, fip) err = s.service.removeServerFloatingIP(server.Id, fip.Id) c.Assert(err, gc.IsNil) } func (s *NovaSuite) TestRemoveServerFloatingIPTwiceFails(c *gc.C) { fip := nova.FloatingIP{Id: "1", IP: "1.2.3.4"} server := nova.ServerDetail{ Id: "sr1", Addresses: map[string][]nova.IPAddress{"private": []nova.IPAddress{}}, } s.createServer(c, server) defer s.deleteServer(c, server) s.createIP(c, fip) defer s.deleteIP(c, fip) err := s.service.addServerFloatingIP(server.Id, fip.Id) c.Assert(err, gc.IsNil) err = s.service.removeServerFloatingIP(server.Id, fip.Id) c.Assert(err, gc.IsNil) err = s.service.removeServerFloatingIP(server.Id, fip.Id) c.Assert(err, gc.ErrorMatches, `itemNotFound: Server "sr1" does not have floating IP 1`) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/novaservice/setup_test.go000066400000000000000000000007161414605405700277150ustar00rootroot00000000000000package novaservice import ( "testing" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/nova" ) func Test(t *testing.T) { gc.TestingT(t) } // checkGroupsInList checks that every group in groups is in groupList. func checkGroupsInList(c *gc.C, groups []nova.SecurityGroup, groupList []nova.SecurityGroup) { for _, g := range groups { for _, gr := range groupList { if g.Id == gr.Id { c.Assert(g, gc.DeepEquals, gr) return } } c.Fail() } } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/openstackservice/000077500000000000000000000000001414605405700262075ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/openstackservice/openstack.go000066400000000000000000000213321414605405700305260ustar00rootroot00000000000000package openstackservice import ( "fmt" "net/http" "net/http/httptest" "strconv" "strings" "gopkg.in/goose.v1/identity" "gopkg.in/goose.v1/testservices/identityservice" "gopkg.in/goose.v1/testservices/neutronmodel" "gopkg.in/goose.v1/testservices/neutronservice" "gopkg.in/goose.v1/testservices/novaservice" "gopkg.in/goose.v1/testservices/swiftservice" ) // Openstack provides an Openstack service double implementation. type Openstack struct { Identity identityservice.IdentityService // Keystone v3 supports serving both V2 and V3 at the same time // this will intend to emulate that behavior. FallbackIdentity identityservice.IdentityService Nova *novaservice.Nova Neutron *neutronservice.Neutron Swift *swiftservice.Swift muxes map[string]*http.ServeMux servers map[string]*httptest.Server // base url of openstack endpoints, might be required to // simmulate response contents such as the ones from // identity discovery. URLs map[string]string } func (openstack *Openstack) AddUser(user, secret, project, authDomain string) *identityservice.UserInfo { uinfo := openstack.Identity.AddUser(user, secret, project, authDomain) if openstack.FallbackIdentity != nil { _ = openstack.FallbackIdentity.AddUser(user, secret, project, authDomain) } return uinfo } // New creates an instance of a full Openstack service double. // An initial user with the specified credentials is registered with the // identity service. This service double manages the httpServers necessary // for Neturon, Nova, Swift and Identity services func New(cred *identity.Credentials, authMode identity.AuthMode, useTLS bool) (*Openstack, []string) { openstack, logMsgs := NewNoSwift(cred, authMode, useTLS) var server *httptest.Server if useTLS { server = httptest.NewTLSServer(nil) } else { server = httptest.NewServer(nil) } logMsgs = append(logMsgs, "swift service started: "+server.URL) openstack.muxes["swift"] = http.NewServeMux() server.Config.Handler = openstack.muxes["swift"] openstack.URLs["swift"] = server.URL openstack.servers["swift"] = server // Create the swift service using only the region base so we emulate real world deployments. regionParts := strings.Split(cred.Region, ".") baseRegion := regionParts[len(regionParts)-1] openstack.Swift = swiftservice.New(openstack.URLs["swift"], "v1", cred.TenantName, baseRegion, openstack.Identity, openstack.FallbackIdentity) // Create container and add image metadata endpoint so that product-streams URLs are included // in the keystone catalog. err := openstack.Swift.AddContainer("imagemetadata") if err != nil { panic(fmt.Errorf("setting up image metadata container: %v", err)) } url := openstack.Swift.Endpoints()[0].PublicURL serviceDef := identityservice.V2Service{ Name: "simplestreams", Type: "product-streams", Endpoints: []identityservice.Endpoint{ {PublicURL: url + "/imagemetadata", Region: cred.Region}, }} service3Def := identityservice.V3Service{ Name: "simplestreams", Type: "product-streams", Endpoints: identityservice.NewV3Endpoints("", "", url+"/imagemetadata", cred.Region), } openstack.Identity.AddService(identityservice.Service{V2: serviceDef, V3: service3Def}) // Add public bucket endpoint so that juju-tools URLs are included in the keystone catalog. serviceDef = identityservice.V2Service{ Name: "juju", Type: "juju-tools", Endpoints: []identityservice.Endpoint{ {PublicURL: url, Region: cred.Region}, }} service3Def = identityservice.V3Service{ Name: "juju", Type: "juju-tools", Endpoints: identityservice.NewV3Endpoints("", "", url, cred.Region), } openstack.Identity.AddService(identityservice.Service{V2: serviceDef, V3: service3Def}) return openstack, logMsgs } // NewNoSwift creates an instance of a partial Openstack service double. // An initial user with the specified credentials is registered with the // identity service. This service double manages the httpServers necessary // for Nova and Identity services func NewNoSwift(cred *identity.Credentials, authMode identity.AuthMode, useTLS bool) (*Openstack, []string) { var openstack Openstack if authMode == identity.AuthKeyPair { openstack = Openstack{ Identity: identityservice.NewKeyPair(), } } else if authMode == identity.AuthUserPassV3 { openstack = Openstack{ Identity: identityservice.NewV3UserPass(), FallbackIdentity: identityservice.NewUserPass(), } } else { openstack = Openstack{ Identity: identityservice.NewUserPass(), FallbackIdentity: identityservice.NewV3UserPass(), } } domain := cred.ProjectDomain if domain == "" { domain = cred.UserDomain } if domain == "" { domain = cred.Domain } if domain == "" { domain = "default" } userInfo := openstack.AddUser(cred.User, cred.Secrets, cred.TenantName, domain) if cred.TenantName == "" { panic("Openstack service double requires a project to be specified.") } if useTLS { openstack.servers = map[string]*httptest.Server{ "identity": httptest.NewTLSServer(nil), "neutron": httptest.NewTLSServer(nil), "nova": httptest.NewTLSServer(nil), } } else { openstack.servers = map[string]*httptest.Server{ "identity": httptest.NewServer(nil), "neutron": httptest.NewServer(nil), "nova": httptest.NewServer(nil), } } openstack.muxes = map[string]*http.ServeMux{ "identity": http.NewServeMux(), "neutron": http.NewServeMux(), "nova": http.NewServeMux(), } for k, v := range openstack.servers { v.Config.Handler = openstack.muxes[k] } cred.URL = openstack.servers["identity"].URL openstack.URLs = make(map[string]string) var logMsgs []string for k, v := range openstack.servers { openstack.URLs[k] = v.URL logMsgs = append(logMsgs, k+" service started: "+openstack.URLs[k]) } openstack.Nova = novaservice.New(openstack.URLs["nova"], "v2", userInfo.TenantId, cred.Region, openstack.Identity, openstack.FallbackIdentity) openstack.Neutron = neutronservice.New(openstack.URLs["neutron"], "v2.0", userInfo.TenantId, cred.Region, openstack.Identity, openstack.FallbackIdentity) return &openstack, logMsgs } // UseNeutronNetworking sets up the openstack service to use neutron networking. func (openstack *Openstack) UseNeutronNetworking() { // Neutron & Nova test doubles share a neutron data model for // FloatingIPs, Networks & SecurityGroups neutronModel := neutronmodel.New() openstack.Nova.AddNeutronModel(neutronModel) openstack.Neutron.AddNeutronModel(neutronModel) } // SetupHTTP attaches all the needed handlers to provide the HTTP API for the Openstack service.. func (openstack *Openstack) SetupHTTP(mux *http.ServeMux) { openstack.Identity.SetupHTTP(openstack.muxes["identity"]) // If there is a FallbackIdentity service also register its urls. if openstack.FallbackIdentity != nil { openstack.FallbackIdentity.SetupHTTP(openstack.muxes["identity"]) } openstack.Neutron.SetupHTTP(openstack.muxes["neutron"]) openstack.Nova.SetupHTTP(openstack.muxes["nova"]) if openstack.Swift != nil { openstack.Swift.SetupHTTP(openstack.muxes["swift"]) } // Handle root calls to be able to return auth information. // Neutron and Nova services must handle api version information. // Swift has no list version api call to make openstack.muxes["identity"].Handle("/", openstack) openstack.Nova.SetupRootHandler(openstack.muxes["nova"]) openstack.Neutron.SetupRootHandler(openstack.muxes["neutron"]) } // Stop closes the Openstack service double http Servers and clears the // related http handling func (openstack *Openstack) Stop() { for _, v := range openstack.servers { v.Config.Handler = nil v.Close() } for k, _ := range openstack.muxes { openstack.muxes[k] = nil } } const authInformationBody = `{"versions": {"values": [{"status": "stable", ` + `"updated": "2015-03-30T00:00:00Z", "media-types": [{"base": "application/json", ` + `"type": "application/vnd.openstack.identity-v3+json"}], "id": "v3.4", "links": ` + `[{"href": "%s/v3/", "rel": "self"}]}, {"status": "stable", "updated": ` + `"2014-04-17T00:00:00Z", "media-types": [{"base": "application/json", ` + `"type": "application/vnd.openstack.identity-v2.0+json"}], "id": "v2.0", ` + `"links": [{"href": "%s/v2.0/", "rel": "self"}, {"href": ` + `"http://docs.openstack.org/", "type": "text/html", "rel": "describedby"}]}]}}` func (openstack *Openstack) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { openstack.Nova.HandleRoot(w, r) return } w.Header().Set("Content-Type", "application/json") body := []byte(fmt.Sprintf(authInformationBody, openstack.URLs["identity"], openstack.URLs["identity"])) // workaround for https://code.google.com/p/go/issues/detail?id=4454 w.Header().Set("Content-Length", strconv.Itoa(len(body))) w.WriteHeader(http.StatusMultipleChoices) w.Write(body) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/service.go000066400000000000000000000025301414605405700246260ustar00rootroot00000000000000package testservices import ( "net/http" "gopkg.in/goose.v1/testservices/hook" "gopkg.in/goose.v1/testservices/identityservice" ) // An HttpService provides the HTTP API for a service double. type HttpService interface { SetupHTTP(mux *http.ServeMux) Stop() } // A ServiceInstance is an Openstack module, one of nova, swift, glance. type ServiceInstance struct { identityservice.ServiceProvider hook.TestService IdentityService identityservice.IdentityService // For Keystone V3, V2 is also accepted as an identity service // this represents that possibility. FallbackIdentityService identityservice.IdentityService Scheme string Hostname string VersionPath string TenantId string Region string RegionID string } // Internal Openstack errors. var RateLimitExceededError = NewRateLimitExceededError() // NoMoreFloatingIPs corresponds to "HTTP 404 Zero floating ips available." var NoMoreFloatingIPs = NewNoMoreFloatingIpsError() // IPLimitExceeded corresponds to "HTTP 413 Maximum number of floating ips exceeded" var IPLimitExceeded = NewIPLimitExceededError() // AvailabilityZoneIsNotAvailable corresponds to // "HTTP 400 The requested availability zone is not available" var AvailabilityZoneIsNotAvailable = NewAvailabilityZoneIsNotAvailableError() golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/swiftservice/000077500000000000000000000000001414605405700253545ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/swiftservice/service.go000066400000000000000000000143041414605405700273450ustar00rootroot00000000000000// Swift double testing service - internal direct API implementation package swiftservice import ( "fmt" "net/url" "sort" "strings" "sync" "time" "gopkg.in/goose.v1/swift" "gopkg.in/goose.v1/testservices" "gopkg.in/goose.v1/testservices/identityservice" ) type object map[string][]byte var _ testservices.HttpService = (*Swift)(nil) var _ identityservice.ServiceProvider = (*Swift)(nil) type Swift struct { testservices.ServiceInstance mu sync.Mutex // protects the remaining fields containers map[string]object } // New creates an instance of the Swift object, given the parameters. func New(hostURL, versionPath, tenantId, region string, identityService, fallbackIdentity identityservice.IdentityService) *Swift { URL, err := url.Parse(hostURL) if err != nil { panic(err) } hostname := URL.Host if !strings.HasSuffix(hostname, "/") { hostname += "/" } swift := &Swift{ containers: make(map[string]object), ServiceInstance: testservices.ServiceInstance{ IdentityService: identityService, FallbackIdentityService: fallbackIdentity, Scheme: URL.Scheme, Hostname: hostname, VersionPath: versionPath, TenantId: tenantId, Region: region, }, } if identityService != nil { identityService.RegisterServiceProvider("swift", "object-store", swift) } return swift } func Stop() { // noop } func (s *Swift) endpointURL(path string) string { // Note: We're using the Openstack Style endpoints for this testservice // Openstack object-storage endpoint url: // http://:/swift/v1 // Rackspace object-storage endpoint url: // https:///v1/MossoCloudFS_ ep := s.Scheme + "://" + s.Hostname + "swift/" + s.VersionPath if path != "" { ep += "/" + strings.TrimLeft(path, "/") } return ep } func (s *Swift) Endpoints() []identityservice.Endpoint { ep := identityservice.Endpoint{ AdminURL: s.endpointURL(""), InternalURL: s.endpointURL(""), PublicURL: s.endpointURL(""), Region: s.Region, } return []identityservice.Endpoint{ep} } func (s *Swift) V3Endpoints() []identityservice.V3Endpoint { url := s.endpointURL("") return identityservice.NewV3Endpoints(url, url, url, s.RegionID) } // HasContainer verifies the given container exists or not. func (s *Swift) HasContainer(name string) bool { s.mu.Lock() _, ok := s.containers[name] s.mu.Unlock() return ok } // GetObject retrieves a given object from its container, returning // the object data or an error. func (s *Swift) GetObject(container, name string) ([]byte, error) { if err := s.ProcessFunctionHook(s, container, name); err != nil { return nil, err } s.mu.Lock() data, ok := s.containers[container][name] s.mu.Unlock() if !ok { return nil, fmt.Errorf("no such object %q in container %q", name, container) } return data, nil } // AddContainer creates a new container with the given name, if it // does not exist. Otherwise an error is returned. func (s *Swift) AddContainer(name string) error { if err := s.ProcessFunctionHook(s, name); err != nil { return err } if s.HasContainer(name) { return fmt.Errorf("container already exists %q", name) } s.mu.Lock() s.containers[name] = make(object) s.mu.Unlock() return nil } // ListContainer lists the objects in the given container. // params contains filtering attributes: prefix, delimiter, marker. // Only prefix is currently supported. func (s *Swift) ListContainer(name string, params map[string]string) ([]swift.ContainerContents, error) { if err := s.ProcessFunctionHook(s, name); err != nil { return nil, err } if ok := s.HasContainer(name); !ok { return nil, fmt.Errorf("no such container %q", name) } s.mu.Lock() items := s.containers[name] s.mu.Unlock() sorted := make([]string, 0, len(items)) prefix := params["prefix"] for filename := range items { if prefix != "" && !strings.HasPrefix(filename, prefix) { continue } sorted = append(sorted, filename) } sort.Strings(sorted) contents := make([]swift.ContainerContents, len(sorted)) var i = 0 for _, filename := range sorted { contents[i] = swift.ContainerContents{ Name: filename, Hash: "", // not implemented LengthBytes: len(items[filename]), ContentType: "application/octet-stream", LastModified: time.Now().Format("2006-01-02 15:04:05"), //not implemented } i++ } return contents, nil } // AddObject creates a new object with the given name in the specified // container, setting the object's data. It's an error if the object // already exists. If the container does not exist, it will be // created. func (s *Swift) AddObject(container, name string, data []byte) error { if err := s.ProcessFunctionHook(s, container, name); err != nil { return err } if _, err := s.GetObject(container, name); err == nil { return fmt.Errorf( "object %q in container %q already exists", name, container) } if ok := s.HasContainer(container); !ok { if err := s.AddContainer(container); err != nil { return err } } s.mu.Lock() s.containers[container][name] = data s.mu.Unlock() return nil } // RemoveContainer deletes an existing container with the given name. func (s *Swift) RemoveContainer(name string) error { if err := s.ProcessFunctionHook(s, name); err != nil { return err } if ok := s.HasContainer(name); !ok { return fmt.Errorf("no such container %q", name) } s.mu.Lock() delete(s.containers, name) s.mu.Unlock() return nil } // RemoveObject deletes an existing object in a given container. func (s *Swift) RemoveObject(container, name string) error { if err := s.ProcessFunctionHook(s, container, name); err != nil { return err } if _, err := s.GetObject(container, name); err != nil { return err } s.mu.Lock() delete(s.containers[container], name) s.mu.Unlock() return nil } // GetURL returns the full URL, which can be used to GET the // object. An error occurs if the object does not exist. func (s *Swift) GetURL(container, object string) (string, error) { if err := s.ProcessFunctionHook(s, container, object); err != nil { return "", err } if _, err := s.GetObject(container, object); err != nil { return "", err } return s.endpointURL(fmt.Sprintf("%s/%s", container, object)), nil } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/swiftservice/service_http.go000066400000000000000000000134421414605405700304060ustar00rootroot00000000000000// Swift double testing service - HTTP API implementation package swiftservice import ( "encoding/json" "io/ioutil" "net/http" "net/url" "strings" ) // verbatim real Swift responses const ( notFoundResponse = `404 Not Found The resource could not be found. ` createdResponse = `201 Created ` acceptedResponse = `202 Accepted The request is accepted for processing. ` ) // handleContainers processes HTTP requests for container management. func (s *Swift) handleContainers(container string, w http.ResponseWriter, r *http.Request) { var err error w.Header().Set("Content-Type", "text/html; charset=UTF-8") exists := s.HasContainer(container) if !exists && r.Method != "PUT" { w.WriteHeader(http.StatusNotFound) w.Write([]byte(notFoundResponse)) return } switch r.Method { case "GET": urlParams, err := url.ParseQuery(r.URL.RawQuery) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } params := make(map[string]string, len(urlParams)) for k := range urlParams { params[k] = urlParams.Get(k) } contents, err := s.ListContainer(container, params) var objdata []byte if err == nil { objdata, err = json.Marshal(contents) } if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) } else { w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json; charset=UF-8") w.Write([]byte(objdata)) } case "DELETE": if err = s.RemoveContainer(container); err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) } else { w.Header().Set("Content-Length", "0") w.WriteHeader(http.StatusNoContent) } case "HEAD": urlParams, err := url.ParseQuery(r.URL.RawQuery) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } params := make(map[string]string, len(urlParams)) for k := range urlParams { params[k] = urlParams.Get(k) } _, err = s.ListContainer(container, params) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) } else { w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json; charset=UF-8") } case "PUT": if exists { w.WriteHeader(http.StatusAccepted) w.Write([]byte(acceptedResponse)) } else { if err = s.AddContainer(container); err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) } else { w.WriteHeader(http.StatusCreated) w.Write([]byte(createdResponse)) } } case "POST": // [sodre]: we don't implement changing ACLs, so this always succeeds. w.WriteHeader(http.StatusAccepted) w.Write([]byte(createdResponse)) default: panic("not implemented request type: " + r.Method) } } // handleObjects processes HTTP requests for object management. func (s *Swift) handleObjects(container, object string, w http.ResponseWriter, r *http.Request) { var err error w.Header().Set("Content-Type", "text/html; charset=UTF-8") if exists := s.HasContainer(container); !exists { w.WriteHeader(http.StatusNotFound) w.Write([]byte(notFoundResponse)) return } objdata, err := s.GetObject(container, object) if err != nil && r.Method != "PUT" { w.WriteHeader(http.StatusNotFound) w.Write([]byte(notFoundResponse)) return } exists := err == nil switch r.Method { case "GET": w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json; charset=UF-8") w.Write([]byte(objdata)) case "DELETE": if err = s.RemoveObject(container, object); err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) } else { w.Header().Set("Content-Length", "0") w.WriteHeader(http.StatusNoContent) } case "HEAD": w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json; charset=UF-8") case "PUT": bodydata, err := ioutil.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } if exists { err = s.RemoveObject(container, object) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) } } if err = s.AddObject(container, object, bodydata); err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) } else { w.WriteHeader(http.StatusCreated) w.Write([]byte(createdResponse)) } default: panic("not implemented request type: " + r.Method) } } // ServeHTTP is the main entry point in the HTTP request processing. func (s *Swift) ServeHTTP(w http.ResponseWriter, r *http.Request) { // TODO(wallyworld) - 2013-02-11 bug=1121682 // we need to support container ACLs so we can have pubic containers. // For public containers, the token is not required to access the files. For now, if the request // does not provide a token, we will let it through and assume a public container is being accessed. token := r.Header.Get("X-Auth-Token") _, err := s.IdentityService.FindUser(token) if err != nil && s.FallbackIdentityService != nil { _, err = s.FallbackIdentityService.FindUser(token) } if token != "" && err != nil { w.WriteHeader(http.StatusUnauthorized) return } path := strings.TrimRight(r.URL.Path, "/") path = strings.Trim(path, "/") parts := strings.SplitN(path, "/", 4) if len(parts) > 2 { parts = parts[2:] if len(parts) == 1 { container := parts[0] s.handleContainers(container, w, r) } else if len(parts) == 2 { container := parts[0] object := parts[1] s.handleObjects(container, object, w, r) } } else { panic("not implemented request: " + r.URL.Path) } } // setupHTTP attaches all the needed handlers to provide the HTTP API. func (s *Swift) SetupHTTP(mux *http.ServeMux) { mux.Handle("/", s) } func (s *Swift) Stop() { // noop } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/swiftservice/service_http_test.go000066400000000000000000000250171414605405700314460ustar00rootroot00000000000000// Swift double testing service - HTTP API tests package swiftservice import ( "bytes" "encoding/json" "io/ioutil" "net/http" "net/url" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/swift" "gopkg.in/goose.v1/testing/httpsuite" "gopkg.in/goose.v1/testservices/identityservice" ) type SwiftHTTPSuite struct { httpsuite.HTTPSuite service *Swift token string } var _ = gc.Suite(&SwiftHTTPSuite{}) type SwiftHTTPSSuite struct { httpsuite.HTTPSuite service *Swift token string } var _ = gc.Suite(&SwiftHTTPSSuite{HTTPSuite: httpsuite.HTTPSuite{UseTLS: true}}) func (s *SwiftHTTPSuite) SetUpSuite(c *gc.C) { s.HTTPSuite.SetUpSuite(c) identityDouble := identityservice.NewUserPass() s.service = New(s.Server.URL, versionPath, tenantId, region, identityDouble, nil) userInfo := identityDouble.AddUser("fred", "secret", "tenant", "default") s.token = userInfo.Token } func (s *SwiftHTTPSuite) SetUpTest(c *gc.C) { s.HTTPSuite.SetUpTest(c) s.service.SetupHTTP(s.Mux) } func (s *SwiftHTTPSuite) TearDownTest(c *gc.C) { s.HTTPSuite.TearDownTest(c) } func (s *SwiftHTTPSuite) TearDownSuite(c *gc.C) { s.HTTPSuite.TearDownSuite(c) } func (s *SwiftHTTPSuite) sendRequest(c *gc.C, method, path string, body []byte, expectedStatusCode int) (resp *http.Response) { return s.sendRequestWithParams(c, method, path, nil, body, expectedStatusCode) } func (s *SwiftHTTPSuite) sendRequestWithParams(c *gc.C, method, path string, params map[string]string, body []byte, expectedStatusCode int) (resp *http.Response) { var req *http.Request var err error URL := s.service.endpointURL(path) if len(params) > 0 { urlParams := make(url.Values, len(params)) for k, v := range params { urlParams.Set(k, v) } URL += "?" + urlParams.Encode() } if body != nil { req, err = http.NewRequest(method, URL, bytes.NewBuffer(body)) } else { req, err = http.NewRequest(method, URL, nil) } c.Assert(err, gc.IsNil) if s.token != "" { req.Header.Add("X-Auth-Token", s.token) } client := http.DefaultClient resp, err = client.Do(req) c.Assert(err, gc.IsNil) c.Assert(resp.StatusCode, gc.Equals, expectedStatusCode) return resp } func (s *SwiftHTTPSuite) ensureNotContainer(name string, c *gc.C) { ok := s.service.HasContainer("test") c.Assert(ok, gc.Equals, false) } func (s *SwiftHTTPSuite) ensureContainer(name string, c *gc.C) { s.ensureNotContainer(name, c) err := s.service.AddContainer("test") c.Assert(err, gc.IsNil) } func (s *SwiftHTTPSuite) removeContainer(name string, c *gc.C) { ok := s.service.HasContainer("test") c.Assert(ok, gc.Equals, true) err := s.service.RemoveContainer("test") c.Assert(err, gc.IsNil) } func (s *SwiftHTTPSuite) ensureNotObject(container, object string, c *gc.C) { _, err := s.service.GetObject(container, object) c.Assert(err, gc.Not(gc.IsNil)) } func (s *SwiftHTTPSuite) ensureObject(container, object string, data []byte, c *gc.C) { s.ensureNotObject(container, object, c) err := s.service.AddObject(container, object, data) c.Assert(err, gc.IsNil) } func (s *SwiftHTTPSuite) ensureObjectData(container, object string, data []byte, c *gc.C) { objdata, err := s.service.GetObject(container, object) c.Assert(err, gc.IsNil) c.Assert(objdata, gc.DeepEquals, data) } func (s *SwiftHTTPSuite) removeObject(container, object string, c *gc.C) { err := s.service.RemoveObject(container, object) c.Assert(err, gc.IsNil) s.ensureNotObject(container, object, c) } func (s *SwiftHTTPSuite) TestPUTContainerMissingCreated(c *gc.C) { s.ensureNotContainer("test", c) s.sendRequest(c, "PUT", "test", nil, http.StatusCreated) s.removeContainer("test", c) } func (s *SwiftHTTPSuite) TestPUTContainerExistsAccepted(c *gc.C) { s.ensureContainer("test", c) s.sendRequest(c, "PUT", "test", nil, http.StatusAccepted) s.removeContainer("test", c) } func (s *SwiftHTTPSuite) TestGETContainerMissingNotFound(c *gc.C) { s.ensureNotContainer("test", c) s.sendRequest(c, "GET", "test", nil, http.StatusNotFound) s.ensureNotContainer("test", c) } func (s *SwiftHTTPSuite) TestGETContainerExistsOK(c *gc.C) { s.ensureContainer("test", c) data := []byte("test data") s.ensureObject("test", "obj", data, c) resp := s.sendRequest(c, "GET", "test", nil, http.StatusOK) defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) c.Assert(err, gc.IsNil) var containerData []swift.ContainerContents err = json.Unmarshal(body, &containerData) c.Assert(err, gc.IsNil) c.Assert(len(containerData), gc.Equals, 1) c.Assert(containerData[0].Name, gc.Equals, "obj") s.removeContainer("test", c) } func (s *SwiftHTTPSuite) TestGETContainerWithPrefix(c *gc.C) { s.ensureContainer("test", c) data := []byte("test data") s.ensureObject("test", "foo", data, c) s.ensureObject("test", "foobar", data, c) resp := s.sendRequestWithParams(c, "GET", "test", map[string]string{"prefix": "foob"}, nil, http.StatusOK) defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) c.Assert(err, gc.IsNil) var containerData []swift.ContainerContents err = json.Unmarshal(body, &containerData) c.Assert(err, gc.IsNil) c.Assert(len(containerData), gc.Equals, 1) c.Assert(containerData[0].Name, gc.Equals, "foobar") s.removeContainer("test", c) } func (s *SwiftHTTPSuite) TestDELETEContainerMissingNotFound(c *gc.C) { s.ensureNotContainer("test", c) s.sendRequest(c, "DELETE", "test", nil, http.StatusNotFound) } func (s *SwiftHTTPSuite) TestDELETEContainerExistsNoContent(c *gc.C) { s.ensureContainer("test", c) s.sendRequest(c, "DELETE", "test", nil, http.StatusNoContent) s.ensureNotContainer("test", c) } func (s *SwiftHTTPSuite) TestPUTObjectMissingCreated(c *gc.C) { s.ensureContainer("test", c) s.ensureNotObject("test", "obj", c) data := []byte("test data") s.sendRequest(c, "PUT", "test/obj", data, http.StatusCreated) s.ensureObjectData("test", "obj", data, c) s.removeContainer("test", c) } func (s *SwiftHTTPSuite) TestPUTObjectExistsCreated(c *gc.C) { data := []byte("test data") s.ensureContainer("test", c) s.ensureObject("test", "obj", data, c) newdata := []byte("new test data") s.sendRequest(c, "PUT", "test/obj", newdata, http.StatusCreated) s.ensureObjectData("test", "obj", newdata, c) s.removeContainer("test", c) } func (s *SwiftHTTPSuite) TestPUTObjectContainerMissingNotFound(c *gc.C) { s.ensureNotContainer("test", c) data := []byte("test data") s.sendRequest(c, "PUT", "test/obj", data, http.StatusNotFound) s.ensureNotContainer("test", c) } func (s *SwiftHTTPSuite) TestGETObjectMissingNotFound(c *gc.C) { s.ensureContainer("test", c) s.ensureNotObject("test", "obj", c) s.sendRequest(c, "GET", "test/obj", nil, http.StatusNotFound) s.removeContainer("test", c) } func (s *SwiftHTTPSuite) TestGETObjectContainerMissingNotFound(c *gc.C) { s.ensureNotContainer("test", c) s.sendRequest(c, "GET", "test/obj", nil, http.StatusNotFound) s.ensureNotContainer("test", c) } func (s *SwiftHTTPSuite) TestGETObjectExistsOK(c *gc.C) { data := []byte("test data") s.ensureContainer("test", c) s.ensureObject("test", "obj", data, c) resp := s.sendRequest(c, "GET", "test/obj", nil, http.StatusOK) s.ensureObjectData("test", "obj", data, c) defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) c.Assert(err, gc.IsNil) c.Assert(body, gc.DeepEquals, data) s.removeContainer("test", c) } func (s *SwiftHTTPSuite) TestDELETEObjectMissingNotFound(c *gc.C) { s.ensureContainer("test", c) s.ensureNotObject("test", "obj", c) s.sendRequest(c, "DELETE", "test/obj", nil, http.StatusNotFound) s.removeContainer("test", c) } func (s *SwiftHTTPSuite) TestDELETEObjectContainerMissingNotFound(c *gc.C) { s.ensureNotContainer("test", c) s.sendRequest(c, "DELETE", "test/obj", nil, http.StatusNotFound) s.ensureNotContainer("test", c) } func (s *SwiftHTTPSuite) TestDELETEObjectExistsNoContent(c *gc.C) { data := []byte("test data") s.ensureContainer("test", c) s.ensureObject("test", "obj", data, c) s.sendRequest(c, "DELETE", "test/obj", nil, http.StatusNoContent) s.removeContainer("test", c) } func (s *SwiftHTTPSuite) TestHEADContainerExistsOK(c *gc.C) { s.ensureContainer("test", c) data := []byte("test data") s.ensureObject("test", "obj", data, c) resp := s.sendRequest(c, "HEAD", "test", nil, http.StatusOK) c.Assert(resp.Header.Get("Date"), gc.Not(gc.Equals), "") defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) c.Assert(err, gc.IsNil) c.Assert(body, gc.DeepEquals, []byte{}) s.removeContainer("test", c) } func (s *SwiftHTTPSuite) TestHEADContainerMissingNotFound(c *gc.C) { s.ensureNotContainer("test", c) s.sendRequest(c, "HEAD", "test", nil, http.StatusNotFound) s.ensureNotContainer("test", c) } func (s *SwiftHTTPSuite) TestHEADObjectExistsOK(c *gc.C) { data := []byte("test data") s.ensureContainer("test", c) s.ensureObject("test", "obj", data, c) resp := s.sendRequest(c, "HEAD", "test/obj", nil, http.StatusOK) s.ensureObjectData("test", "obj", data, c) c.Assert(resp.Header.Get("Date"), gc.Not(gc.Equals), "") defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) c.Assert(err, gc.IsNil) c.Assert(body, gc.DeepEquals, []byte{}) s.removeContainer("test", c) } func (s *SwiftHTTPSuite) TestHEADObjectMissingNotFound(c *gc.C) { s.ensureContainer("test", c) s.ensureNotObject("test", "obj", c) s.sendRequest(c, "HEAD", "test/obj", nil, http.StatusNotFound) s.removeContainer("test", c) } func (s *SwiftHTTPSuite) TestUnauthorizedFails(c *gc.C) { oldtoken := s.token defer func() { s.token = oldtoken }() // TODO(wallyworld) - 2013-02-11 bug=1121682 // until ACLs are supported, empty tokens are assumed to be used when we need to access a public container. // token = "" // s.sendRequest(c, "GET", "test", nil, http.StatusUnauthorized) s.token = "invalid" s.sendRequest(c, "PUT", "test", nil, http.StatusUnauthorized) s.sendRequest(c, "DELETE", "test", nil, http.StatusUnauthorized) } func (s *SwiftHTTPSSuite) SetUpSuite(c *gc.C) { s.HTTPSuite.SetUpSuite(c) identityDouble := identityservice.NewUserPass() userInfo := identityDouble.AddUser("fred", "secret", "tenant", "default") s.token = userInfo.Token c.Assert(s.Server.URL[:8], gc.Equals, "https://") s.service = New(s.Server.URL, versionPath, userInfo.TenantId, region, identityDouble, nil) } func (s *SwiftHTTPSSuite) TearDownSuite(c *gc.C) { s.HTTPSuite.TearDownSuite(c) } func (s *SwiftHTTPSSuite) SetUpTest(c *gc.C) { s.HTTPSuite.SetUpTest(c) s.service.SetupHTTP(s.Mux) } func (s *SwiftHTTPSSuite) TearDownTest(c *gc.C) { s.HTTPSuite.TearDownTest(c) } func (s *SwiftHTTPSSuite) TestHasHTTPSServiceURL(c *gc.C) { endpoints := s.service.Endpoints() c.Assert(endpoints[0].PublicURL[:8], gc.Equals, "https://") } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/swiftservice/service_test.go000066400000000000000000000072771414605405700304170ustar00rootroot00000000000000// Swift double testing service - internal direct API tests package swiftservice import ( "fmt" gc "gopkg.in/check.v1" ) type SwiftServiceSuite struct { service *Swift } var region = "region" // not really used here var hostname = "http://localhost" // not really used here var versionPath = "v2" // not really used here var tenantId = "tenant" // not really used here var _ = gc.Suite(&SwiftServiceSuite{}) func (s *SwiftServiceSuite) SetUpSuite(c *gc.C) { s.service = New(hostname, versionPath, tenantId, region, nil, nil) } func (s *SwiftServiceSuite) TestAddHasRemoveContainer(c *gc.C) { ok := s.service.HasContainer("test") c.Assert(ok, gc.Equals, false) err := s.service.AddContainer("test") c.Assert(err, gc.IsNil) ok = s.service.HasContainer("test") c.Assert(ok, gc.Equals, true) err = s.service.RemoveContainer("test") c.Assert(err, gc.IsNil) ok = s.service.HasContainer("test") c.Assert(ok, gc.Equals, false) } func (s *SwiftServiceSuite) TestAddGetRemoveObject(c *gc.C) { _, err := s.service.GetObject("test", "obj") c.Assert(err, gc.Not(gc.IsNil)) err = s.service.AddContainer("test") c.Assert(err, gc.IsNil) ok := s.service.HasContainer("test") c.Assert(ok, gc.Equals, true) data := []byte("test data") err = s.service.AddObject("test", "obj", data) c.Assert(err, gc.IsNil) objdata, err := s.service.GetObject("test", "obj") c.Assert(err, gc.IsNil) c.Assert(objdata, gc.DeepEquals, data) err = s.service.RemoveObject("test", "obj") c.Assert(err, gc.IsNil) _, err = s.service.GetObject("test", "obj") c.Assert(err, gc.Not(gc.IsNil)) err = s.service.RemoveContainer("test") c.Assert(err, gc.IsNil) ok = s.service.HasContainer("test") c.Assert(ok, gc.Equals, false) } func (s *SwiftServiceSuite) TestRemoveContainerWithObjects(c *gc.C) { ok := s.service.HasContainer("test") c.Assert(ok, gc.Equals, false) err := s.service.AddObject("test", "obj", []byte("test data")) c.Assert(err, gc.IsNil) err = s.service.RemoveContainer("test") c.Assert(err, gc.IsNil) _, err = s.service.GetObject("test", "obj") c.Assert(err, gc.Not(gc.IsNil)) } func (s *SwiftServiceSuite) TestGetURL(c *gc.C) { ok := s.service.HasContainer("test") c.Assert(ok, gc.Equals, false) err := s.service.AddContainer("test") c.Assert(err, gc.IsNil) data := []byte("test data") err = s.service.AddObject("test", "obj", data) c.Assert(err, gc.IsNil) url, err := s.service.GetURL("test", "obj") c.Assert(err, gc.IsNil) c.Assert(url, gc.Equals, fmt.Sprintf("%s/swift/%s/test/obj", hostname, versionPath)) err = s.service.RemoveContainer("test") c.Assert(err, gc.IsNil) ok = s.service.HasContainer("test") c.Assert(ok, gc.Equals, false) } func (s *SwiftServiceSuite) TestListContainer(c *gc.C) { err := s.service.AddContainer("test") c.Assert(err, gc.IsNil) data := []byte("test data") err = s.service.AddObject("test", "obj", data) c.Assert(err, gc.IsNil) containerData, err := s.service.ListContainer("test", nil) c.Assert(err, gc.IsNil) c.Assert(len(containerData), gc.Equals, 1) c.Assert(containerData[0].Name, gc.Equals, "obj") err = s.service.RemoveContainer("test") c.Assert(err, gc.IsNil) } func (s *SwiftServiceSuite) TestListContainerWithPrefix(c *gc.C) { err := s.service.AddContainer("test") c.Assert(err, gc.IsNil) data := []byte("test data") err = s.service.AddObject("test", "foo", data) c.Assert(err, gc.IsNil) err = s.service.AddObject("test", "foobar", data) c.Assert(err, gc.IsNil) containerData, err := s.service.ListContainer("test", map[string]string{"prefix": "foob"}) c.Assert(err, gc.IsNil) c.Assert(len(containerData), gc.Equals, 1) c.Assert(containerData[0].Name, gc.Equals, "foobar") err = s.service.RemoveContainer("test") c.Assert(err, gc.IsNil) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/testservices/swiftservice/setup_test.go000066400000000000000000000001621414605405700301010ustar00rootroot00000000000000package swiftservice import ( "testing" gc "gopkg.in/check.v1" ) func Test(t *testing.T) { gc.TestingT(t) } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/tools/000077500000000000000000000000001414605405700212545ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/tools/secgroup-delete-all/000077500000000000000000000000001414605405700251115ustar00rootroot00000000000000golang-gopkg-goose.v1-0.0~git20170406.3228e4f/tools/secgroup-delete-all/main.go000066400000000000000000000031471414605405700263710ustar00rootroot00000000000000package main import ( "fmt" "io" "os" "github.com/juju/gnuflag" "gopkg.in/goose.v1/client" "gopkg.in/goose.v1/identity" "gopkg.in/goose.v1/nova" ) // DeleteAll destroys all security groups except the default func DeleteAll(w io.Writer, osn *nova.Client) (err error) { groups, err := osn.ListSecurityGroups() if err != nil { return err } deleted := 0 failed := 0 for _, group := range groups { if group.Name != "default" { err := osn.DeleteSecurityGroup(group.Id) if err != nil { failed++ } else { deleted++ } } } if deleted != 0 { fmt.Fprintf(w, "%d security groups deleted.\n", deleted) } else if failed == 0 { fmt.Fprint(w, "No security groups to delete.\n") } if failed != 0 { fmt.Fprintf(w, "%d security groups could not be deleted.\n", failed) } return nil } func createNovaClient(authMode identity.AuthMode) (osn *nova.Client, err error) { creds, err := identity.CompleteCredentialsFromEnv() if err != nil { return nil, err } osc := client.NewClient(creds, authMode, nil) return nova.New(osc), nil } var authModeFlag = gnuflag.String("auth-mode", "userpass", "type of authentication to use") var authModes = map[string]identity.AuthMode{ "userpass": identity.AuthUserPass, "legacy": identity.AuthLegacy, } func main() { gnuflag.Parse(true) mode, ok := authModes[*authModeFlag] if !ok { fmt.Fprintf(os.Stderr, "error: no such auth-mode %q\n", *authModeFlag) os.Exit(1) } novaclient, err := createNovaClient(mode) if err == nil { err = DeleteAll(os.Stdout, novaclient) } if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/tools/secgroup-delete-all/main_test.go000066400000000000000000000053011414605405700274220ustar00rootroot00000000000000package main import ( "bytes" "fmt" "testing" gc "gopkg.in/check.v1" "gopkg.in/goose.v1/client" "gopkg.in/goose.v1/identity" "gopkg.in/goose.v1/nova" "gopkg.in/goose.v1/testing/httpsuite" "gopkg.in/goose.v1/testservices/hook" "gopkg.in/goose.v1/testservices/openstackservice" ) func Test(t *testing.T) { gc.TestingT(t) } const ( username = "auser" password = "apass" region = "aregion" tenant = "1" ) type ToolSuite struct { httpsuite.HTTPSuite creds *identity.Credentials } var _ = gc.Suite(&ToolSuite{}) // GZ 2013-01-21: Should require EnvSuite for this, but clashes with HTTPSuite func createNovaClientFromCreds(creds *identity.Credentials) *nova.Client { osc := client.NewClient(creds, identity.AuthUserPass, nil) return nova.New(osc) } func (s *ToolSuite) makeServices(c *gc.C) (*openstackservice.Openstack, *nova.Client) { creds := &identity.Credentials{ URL: s.Server.URL, User: username, Secrets: password, Region: region, TenantName: tenant, } openstack, _ := openstackservice.New(creds, identity.AuthUserPass, false) openstack.SetupHTTP(s.Mux) return openstack, createNovaClientFromCreds(creds) } func (s *ToolSuite) TestNoGroups(c *gc.C) { _, nova := s.makeServices(c) var buf bytes.Buffer err := DeleteAll(&buf, nova) c.Assert(err, gc.IsNil) c.Assert(string(buf.Bytes()), gc.Equals, "No security groups to delete.\n") } func (s *ToolSuite) TestTwoGroups(c *gc.C) { _, novaClient := s.makeServices(c) novaClient.CreateSecurityGroup("group-a", "A group") novaClient.CreateSecurityGroup("group-b", "Another group") var buf bytes.Buffer err := DeleteAll(&buf, novaClient) c.Assert(err, gc.IsNil) c.Assert(string(buf.Bytes()), gc.Equals, "2 security groups deleted.\n") } // This group is one for which we will simulate a deletion error in the following test. var doNotDelete *nova.SecurityGroup // deleteGroupError hook raises an error if a group with id 2 is deleted. func deleteGroupError(s hook.ServiceControl, args ...interface{}) error { groupId := args[0].(string) if groupId == doNotDelete.Id { return fmt.Errorf("cannot delete group %s", groupId) } return nil } func (s *ToolSuite) TestUndeleteableGroup(c *gc.C) { os, novaClient := s.makeServices(c) novaClient.CreateSecurityGroup("group-a", "A group") doNotDelete, _ = novaClient.CreateSecurityGroup("group-b", "Another group") novaClient.CreateSecurityGroup("group-c", "Yet another group") cleanup := os.Nova.RegisterControlPoint("removeSecurityGroup", deleteGroupError) defer cleanup() var buf bytes.Buffer err := DeleteAll(&buf, novaClient) c.Assert(err, gc.IsNil) c.Assert(string(buf.Bytes()), gc.Equals, "2 security groups deleted.\n1 security groups could not be deleted.\n") } golang-gopkg-goose.v1-0.0~git20170406.3228e4f/version.go000066400000000000000000000006361414605405700221350ustar00rootroot00000000000000// Copyright 2013 Canonical Ltd. // Licensed under the LGPLv3, see COPYING and COPYING.LESSER file for details. package goose import ( "fmt" ) type VersionNum struct { Major int Minor int Micro int } func (v *VersionNum) String() string { return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Micro) } var VersionNumber = VersionNum{ Major: 0, Minor: 1, Micro: 0, } var Version = VersionNumber.String() golang-gopkg-goose.v1-0.0~git20170406.3228e4f/version_test.go000066400000000000000000000005351414605405700231720ustar00rootroot00000000000000// Copyright 2013 Canonical Ltd. // Licensed under the LGPLv3, see COPYING and COPYING.LESSER file for details. package goose import ( gc "gopkg.in/check.v1" ) type VersionTestSuite struct { } var _ = gc.Suite(&VersionTestSuite{}) func (s *VersionTestSuite) TestStringMatches(c *gc.C) { c.Assert(Version, gc.Equals, VersionNumber.String()) }